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                 mInputChannel, mMessageQueue);
81 
82         mCloseGuard.open("InputEventReceiver.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      */
143     // Called from native code.
onFocusEvent(boolean hasFocus)144     public void onFocusEvent(boolean hasFocus) {
145     }
146 
147     /**
148      * Called when a Pointer Capture event is received.
149      *
150      * @param pointerCaptureEnabled if true, the window associated with this input channel has just
151      *                              received Pointer Capture
152      *                              if false, the window associated with this input channel has just
153      *                              lost Pointer Capture
154      * @see View#requestPointerCapture()
155      * @see View#releasePointerCapture()
156      */
157     // Called from native code.
onPointerCaptureEvent(boolean pointerCaptureEnabled)158     public void onPointerCaptureEvent(boolean pointerCaptureEnabled) {
159     }
160 
161     /**
162      * Called when a drag event is received, from native code.
163      *
164      * @param isExiting if false, the window associated with this input channel has just received
165      *                 drag
166      *                 if true, the window associated with this input channel has just lost drag
167      */
onDragEvent(boolean isExiting, float x, float y)168     public void onDragEvent(boolean isExiting, float x, float y) {
169     }
170 
171     /**
172      * Called when the display for the window associated with the input channel has entered or
173      * exited touch mode.
174      *
175      * @param inTouchMode {@code true} if the display showing the window associated with the
176      *                    input channel entered touch mode or {@code false} if left touch mode
177      */
onTouchModeChanged(boolean inTouchMode)178     public void onTouchModeChanged(boolean inTouchMode) {
179     }
180 
181     /**
182      * Called when a batched input event is pending.
183      *
184      * The batched input event will continue to accumulate additional movement
185      * samples until the recipient calls {@link #consumeBatchedInputEvents} or
186      * an event is received that ends the batch and causes it to be consumed
187      * immediately (such as a pointer up event).
188      * @param source The source of the batched event.
189      */
onBatchedInputEventPending(int source)190     public void onBatchedInputEventPending(int source) {
191         consumeBatchedInputEvents(-1);
192     }
193 
194     /**
195      * Finishes an input event and indicates whether it was handled.
196      * Must be called on the same Looper thread to which the receiver is attached.
197      *
198      * @param event The input event that was finished.
199      * @param handled True if the event was handled.
200      */
finishInputEvent(InputEvent event, boolean handled)201     public final void finishInputEvent(InputEvent event, boolean handled) {
202         if (event == null) {
203             throw new IllegalArgumentException("event must not be null");
204         }
205         if (mReceiverPtr == 0) {
206             Log.w(TAG, "Attempted to finish an input event but the input event "
207                     + "receiver has already been disposed.");
208         } else {
209             int index = mSeqMap.indexOfKey(event.getSequenceNumber());
210             if (index < 0) {
211                 Log.w(TAG, "Attempted to finish an input event that is not in progress.");
212             } else {
213                 int seq = mSeqMap.valueAt(index);
214                 mSeqMap.removeAt(index);
215                 nativeFinishInputEvent(mReceiverPtr, seq, handled);
216             }
217         }
218         event.recycleIfNeededAfterDispatch();
219     }
220 
221     /**
222      * Report the timing / latency information for a specific input event.
223      */
reportTimeline(int inputEventId, long gpuCompletedTime, long presentTime)224     public final void reportTimeline(int inputEventId, long gpuCompletedTime, long presentTime) {
225         Trace.traceBegin(Trace.TRACE_TAG_INPUT, "reportTimeline");
226         nativeReportTimeline(mReceiverPtr, inputEventId, gpuCompletedTime, presentTime);
227         Trace.traceEnd(Trace.TRACE_TAG_INPUT);
228     }
229 
230     /**
231      * Consumes all pending batched input events.
232      * Must be called on the same Looper thread to which the receiver is attached.
233      *
234      * This method forces all batched input events to be delivered immediately.
235      * Should be called just before animating or drawing a new frame in the UI.
236      *
237      * @param frameTimeNanos The time in the {@link System#nanoTime()} time base
238      * when the current display frame started rendering, or -1 if unknown.
239      *
240      * @return Whether a batch was consumed
241      */
consumeBatchedInputEvents(long frameTimeNanos)242     public final boolean consumeBatchedInputEvents(long frameTimeNanos) {
243         if (mReceiverPtr == 0) {
244             Log.w(TAG, "Attempted to consume batched input events but the input event "
245                     + "receiver has already been disposed.");
246         } else {
247             return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
248         }
249         return false;
250     }
251 
252     /**
253      * @return Returns a token to identify the input channel.
254      */
getToken()255     public IBinder getToken() {
256         if (mInputChannel == null) {
257             return null;
258         }
259         return mInputChannel.getToken();
260     }
261 
262     // Called from native code.
263     @SuppressWarnings("unused")
264     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
dispatchInputEvent(int seq, InputEvent event)265     private void dispatchInputEvent(int seq, InputEvent event) {
266         mSeqMap.put(event.getSequenceNumber(), seq);
267         onInputEvent(event);
268     }
269 
270     /**
271      * Dump the state of this InputEventReceiver to the writer.
272      * @param prefix the prefix (typically whitespace padding) to append in front of each line
273      * @param writer the writer where the dump should be written
274      */
dump(String prefix, PrintWriter writer)275     public void dump(String prefix, PrintWriter writer) {
276         writer.println(prefix + getClass().getName());
277         writer.println(prefix + " mInputChannel: " + mInputChannel);
278         writer.println(prefix + " mSeqMap: " + mSeqMap);
279         writer.println(prefix + " mReceiverPtr:\n" + nativeDump(mReceiverPtr, prefix + "  "));
280     }
281 
282     /**
283      * Factory for InputEventReceiver
284      */
285     public interface Factory {
286         /**
287          * Create a new InputReceiver for a given inputChannel
288          */
createInputEventReceiver(InputChannel inputChannel, Looper looper)289         InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);
290     }
291 }
292