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