1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
|
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.Trace;
import android.util.Log;
import android.util.SparseIntArray;
import dalvik.system.CloseGuard;
import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
/**
* Provides a low-level mechanism for an application to receive input events.
* @hide
*/
public abstract class InputEventReceiver {
private static final String TAG = "InputEventReceiver";
private final CloseGuard mCloseGuard = CloseGuard.get();
private long mReceiverPtr;
// We keep references to the input channel and message queue objects here so that
// they are not GC'd while the native peer of the receiver is using them.
private InputChannel mInputChannel;
private MessageQueue mMessageQueue;
// Map from InputEvent sequence numbers to dispatcher sequence numbers.
private final SparseIntArray mSeqMap = new SparseIntArray();
private static native long nativeInit(WeakReference<InputEventReceiver> receiver,
InputChannel inputChannel, MessageQueue messageQueue);
private static native void nativeDispose(long receiverPtr);
private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
private static native void nativeReportTimeline(long receiverPtr, int inputEventId,
long gpuCompletedTime, long presentTime);
private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
long frameTimeNanos);
private static native String nativeDump(long receiverPtr, String prefix);
/**
* Creates an input event receiver bound to the specified input channel.
*
* @param inputChannel The input channel.
* @param looper The looper to use when invoking callbacks.
*/
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
if (inputChannel == null) {
throw new IllegalArgumentException("inputChannel must not be null");
}
if (looper == null) {
throw new IllegalArgumentException("looper must not be null");
}
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
mInputChannel, mMessageQueue);
mCloseGuard.open("InputEventReceiver.dispose");
}
@Override
protected void finalize() throws Throwable {
try {
dispose(true);
} finally {
super.finalize();
}
}
/**
* Disposes the receiver.
* Must be called on the same Looper thread to which the receiver is attached.
*/
public void dispose() {
dispose(false);
}
private void dispose(boolean finalized) {
if (mCloseGuard != null) {
if (finalized) {
mCloseGuard.warnIfOpen();
}
mCloseGuard.close();
}
if (mReceiverPtr != 0) {
nativeDispose(mReceiverPtr);
mReceiverPtr = 0;
}
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
mMessageQueue = null;
Reference.reachabilityFence(this);
}
/**
* Called when an input event is received.
* The recipient should process the input event and then call {@link #finishInputEvent}
* to indicate whether the event was handled. No new input events will be received
* until {@link #finishInputEvent} is called.
*
* @param event The input event that was received.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void onInputEvent(InputEvent event) {
finishInputEvent(event, false);
}
/**
* Called when a focus event is received.
*
* @param hasFocus if true, the window associated with this input channel has just received
* focus
* if false, the window associated with this input channel has just lost focus
*/
// Called from native code.
public void onFocusEvent(boolean hasFocus) {
}
/**
* Called when a Pointer Capture event is received.
*
* @param pointerCaptureEnabled if true, the window associated with this input channel has just
* received Pointer Capture
* if false, the window associated with this input channel has just
* lost Pointer Capture
* @see View#requestPointerCapture()
* @see View#releasePointerCapture()
*/
// Called from native code.
public void onPointerCaptureEvent(boolean pointerCaptureEnabled) {
}
/**
* Called when a drag event is received, from native code.
*
* @param isExiting if false, the window associated with this input channel has just received
* drag
* if true, the window associated with this input channel has just lost drag
*/
public void onDragEvent(boolean isExiting, float x, float y) {
}
/**
* Called when the display for the window associated with the input channel has entered or
* exited touch mode.
*
* @param inTouchMode {@code true} if the display showing the window associated with the
* input channel entered touch mode or {@code false} if left touch mode
*/
public void onTouchModeChanged(boolean inTouchMode) {
}
/**
* Called when a batched input event is pending.
*
* The batched input event will continue to accumulate additional movement
* samples until the recipient calls {@link #consumeBatchedInputEvents} or
* an event is received that ends the batch and causes it to be consumed
* immediately (such as a pointer up event).
* @param source The source of the batched event.
*/
public void onBatchedInputEventPending(int source) {
consumeBatchedInputEvents(-1);
}
/**
* Finishes an input event and indicates whether it was handled.
* Must be called on the same Looper thread to which the receiver is attached.
*
* @param event The input event that was finished.
* @param handled True if the event was handled.
*/
public final void finishInputEvent(InputEvent event, boolean handled) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to finish an input event but the input event "
+ "receiver has already been disposed.");
} else {
int index = mSeqMap.indexOfKey(event.getSequenceNumber());
if (index < 0) {
Log.w(TAG, "Attempted to finish an input event that is not in progress.");
} else {
int seq = mSeqMap.valueAt(index);
mSeqMap.removeAt(index);
nativeFinishInputEvent(mReceiverPtr, seq, handled);
}
}
event.recycleIfNeededAfterDispatch();
}
/**
* Report the timing / latency information for a specific input event.
*/
public final void reportTimeline(int inputEventId, long gpuCompletedTime, long presentTime) {
Trace.traceBegin(Trace.TRACE_TAG_INPUT, "reportTimeline");
nativeReportTimeline(mReceiverPtr, inputEventId, gpuCompletedTime, presentTime);
Trace.traceEnd(Trace.TRACE_TAG_INPUT);
}
/**
* Consumes all pending batched input events.
* Must be called on the same Looper thread to which the receiver is attached.
*
* This method forces all batched input events to be delivered immediately.
* Should be called just before animating or drawing a new frame in the UI.
*
* @param frameTimeNanos The time in the {@link System#nanoTime()} time base
* when the current display frame started rendering, or -1 if unknown.
*
* @return Whether a batch was consumed
*/
public final boolean consumeBatchedInputEvents(long frameTimeNanos) {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to consume batched input events but the input event "
+ "receiver has already been disposed.");
} else {
return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
}
return false;
}
/**
* @return Returns a token to identify the input channel.
*/
public IBinder getToken() {
if (mInputChannel == null) {
return null;
}
return mInputChannel.getToken();
}
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
/**
* Dump the state of this InputEventReceiver to the writer.
* @param prefix the prefix (typically whitespace padding) to append in front of each line
* @param writer the writer where the dump should be written
*/
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + getClass().getName());
writer.println(prefix + " mInputChannel: " + mInputChannel);
writer.println(prefix + " mSeqMap: " + mSeqMap);
writer.println(prefix + " mReceiverPtr:\n" + nativeDump(mReceiverPtr, prefix + " "));
}
/**
* Factory for InputEventReceiver
*/
public interface Factory {
/**
* Create a new InputReceiver for a given inputChannel
*/
InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);
}
}
|