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); nativeReportTimeline(long receiverPtr, int inputEventId, long gpuCompletedTime, long presentTime)57 private static native void nativeReportTimeline(long receiverPtr, int inputEventId, 58 long gpuCompletedTime, long presentTime); nativeConsumeBatchedInputEvents(long receiverPtr, long frameTimeNanos)59 private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr, 60 long frameTimeNanos); nativeDump(long receiverPtr, String prefix)61 private static native String nativeDump(long receiverPtr, String prefix); 62 63 /** 64 * Creates an input event receiver bound to the specified input channel. 65 * 66 * @param inputChannel The input channel. 67 * @param looper The looper to use when invoking callbacks. 68 */ InputEventReceiver(InputChannel inputChannel, Looper looper)69 public InputEventReceiver(InputChannel inputChannel, Looper looper) { 70 if (inputChannel == null) { 71 throw new IllegalArgumentException("inputChannel must not be null"); 72 } 73 if (looper == null) { 74 throw new IllegalArgumentException("looper must not be null"); 75 } 76 77 mInputChannel = inputChannel; 78 mMessageQueue = looper.getQueue(); 79 mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this), 80 inputChannel, mMessageQueue); 81 82 mCloseGuard.open("dispose"); 83 } 84 85 @Override finalize()86 protected void finalize() throws Throwable { 87 try { 88 dispose(true); 89 } finally { 90 super.finalize(); 91 } 92 } 93 94 /** 95 * Disposes the receiver. 96 * Must be called on the same Looper thread to which the receiver is attached. 97 */ dispose()98 public void dispose() { 99 dispose(false); 100 } 101 dispose(boolean finalized)102 private void dispose(boolean finalized) { 103 if (mCloseGuard != null) { 104 if (finalized) { 105 mCloseGuard.warnIfOpen(); 106 } 107 mCloseGuard.close(); 108 } 109 110 if (mReceiverPtr != 0) { 111 nativeDispose(mReceiverPtr); 112 mReceiverPtr = 0; 113 } 114 115 if (mInputChannel != null) { 116 mInputChannel.dispose(); 117 mInputChannel = null; 118 } 119 mMessageQueue = null; 120 Reference.reachabilityFence(this); 121 } 122 123 /** 124 * Called when an input event is received. 125 * The recipient should process the input event and then call {@link #finishInputEvent} 126 * to indicate whether the event was handled. No new input events will be received 127 * until {@link #finishInputEvent} is called. 128 * 129 * @param event The input event that was received. 130 */ 131 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) onInputEvent(InputEvent event)132 public void onInputEvent(InputEvent event) { 133 finishInputEvent(event, false); 134 } 135 136 /** 137 * Called when a focus event is received. 138 * 139 * @param hasFocus if true, the window associated with this input channel has just received 140 * focus 141 * if false, the window associated with this input channel has just lost focus 142 * @param inTouchMode if true, the device is in touch mode 143 * if false, the device is not in touch mode 144 */ 145 // Called from native code. onFocusEvent(boolean hasFocus, boolean inTouchMode)146 public void onFocusEvent(boolean hasFocus, boolean inTouchMode) { 147 } 148 149 /** 150 * Called when a Pointer Capture event is received. 151 * 152 * @param pointerCaptureEnabled if true, the window associated with this input channel has just 153 * received Pointer Capture 154 * if false, the window associated with this input channel has just 155 * lost Pointer Capture 156 * @see View#requestPointerCapture() 157 * @see View#releasePointerCapture() 158 */ 159 // Called from native code. onPointerCaptureEvent(boolean pointerCaptureEnabled)160 public void onPointerCaptureEvent(boolean pointerCaptureEnabled) { 161 } 162 163 /** 164 * Called when a drag event is received, from native code. 165 * 166 * @param isExiting if false, the window associated with this input channel has just received 167 * drag 168 * if true, the window associated with this input channel has just lost drag 169 */ onDragEvent(boolean isExiting, float x, float y)170 public void onDragEvent(boolean isExiting, float x, float y) { 171 } 172 173 /** 174 * Called when a batched input event is pending. 175 * 176 * The batched input event will continue to accumulate additional movement 177 * samples until the recipient calls {@link #consumeBatchedInputEvents} or 178 * an event is received that ends the batch and causes it to be consumed 179 * immediately (such as a pointer up event). 180 * @param source The source of the batched event. 181 */ onBatchedInputEventPending(int source)182 public void onBatchedInputEventPending(int source) { 183 consumeBatchedInputEvents(-1); 184 } 185 186 /** 187 * Finishes an input event and indicates whether it was handled. 188 * Must be called on the same Looper thread to which the receiver is attached. 189 * 190 * @param event The input event that was finished. 191 * @param handled True if the event was handled. 192 */ finishInputEvent(InputEvent event, boolean handled)193 public final void finishInputEvent(InputEvent event, boolean handled) { 194 if (event == null) { 195 throw new IllegalArgumentException("event must not be null"); 196 } 197 if (mReceiverPtr == 0) { 198 Log.w(TAG, "Attempted to finish an input event but the input event " 199 + "receiver has already been disposed."); 200 } else { 201 int index = mSeqMap.indexOfKey(event.getSequenceNumber()); 202 if (index < 0) { 203 Log.w(TAG, "Attempted to finish an input event that is not in progress."); 204 } else { 205 int seq = mSeqMap.valueAt(index); 206 mSeqMap.removeAt(index); 207 nativeFinishInputEvent(mReceiverPtr, seq, handled); 208 } 209 } 210 event.recycleIfNeededAfterDispatch(); 211 } 212 213 /** 214 * Report the timing / latency information for a specific input event. 215 */ reportTimeline(int inputEventId, long gpuCompletedTime, long presentTime)216 public final void reportTimeline(int inputEventId, long gpuCompletedTime, long presentTime) { 217 Trace.traceBegin(Trace.TRACE_TAG_INPUT, "reportTimeline"); 218 nativeReportTimeline(mReceiverPtr, inputEventId, gpuCompletedTime, presentTime); 219 Trace.traceEnd(Trace.TRACE_TAG_INPUT); 220 } 221 222 /** 223 * Consumes all pending batched input events. 224 * Must be called on the same Looper thread to which the receiver is attached. 225 * 226 * This method forces all batched input events to be delivered immediately. 227 * Should be called just before animating or drawing a new frame in the UI. 228 * 229 * @param frameTimeNanos The time in the {@link System#nanoTime()} time base 230 * when the current display frame started rendering, or -1 if unknown. 231 * 232 * @return Whether a batch was consumed 233 */ consumeBatchedInputEvents(long frameTimeNanos)234 public final boolean consumeBatchedInputEvents(long frameTimeNanos) { 235 if (mReceiverPtr == 0) { 236 Log.w(TAG, "Attempted to consume batched input events but the input event " 237 + "receiver has already been disposed."); 238 } else { 239 return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos); 240 } 241 return false; 242 } 243 244 /** 245 * @return Returns a token to identify the input channel. 246 */ getToken()247 public IBinder getToken() { 248 if (mInputChannel == null) { 249 return null; 250 } 251 return mInputChannel.getToken(); 252 } 253 254 // Called from native code. 255 @SuppressWarnings("unused") 256 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) dispatchInputEvent(int seq, InputEvent event)257 private void dispatchInputEvent(int seq, InputEvent event) { 258 mSeqMap.put(event.getSequenceNumber(), seq); 259 onInputEvent(event); 260 } 261 262 /** 263 * Dump the state of this InputEventReceiver to the writer. 264 * @param prefix the prefix (typically whitespace padding) to append in front of each line 265 * @param writer the writer where the dump should be written 266 */ dump(String prefix, PrintWriter writer)267 public void dump(String prefix, PrintWriter writer) { 268 writer.println(prefix + getClass().getName()); 269 writer.println(prefix + " mInputChannel: " + mInputChannel); 270 writer.println(prefix + " mSeqMap: " + mSeqMap); 271 writer.println(prefix + " mReceiverPtr:\n" + nativeDump(mReceiverPtr, prefix + " ")); 272 } 273 274 /** 275 * Factory for InputEventReceiver 276 */ 277 public interface Factory { 278 /** 279 * Create a new InputReceiver for a given inputChannel 280 */ createInputEventReceiver(InputChannel inputChannel, Looper looper)281 InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper); 282 } 283 } 284