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