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.Looper;
21 import android.os.MessageQueue;
22 import android.util.Log;
23 import android.util.SparseIntArray;
24 
25 import dalvik.system.CloseGuard;
26 
27 import java.lang.ref.WeakReference;
28 
29 /**
30  * Provides a low-level mechanism for an application to receive input events.
31  * @hide
32  */
33 public abstract class InputEventReceiver {
34     private static final String TAG = "InputEventReceiver";
35 
36     private final CloseGuard mCloseGuard = CloseGuard.get();
37 
38     private long mReceiverPtr;
39 
40     // We keep references to the input channel and message queue objects here so that
41     // they are not GC'd while the native peer of the receiver is using them.
42     private InputChannel mInputChannel;
43     private MessageQueue mMessageQueue;
44 
45     // Map from InputEvent sequence numbers to dispatcher sequence numbers.
46     private final SparseIntArray mSeqMap = new SparseIntArray();
47 
nativeInit(WeakReference<InputEventReceiver> receiver, InputChannel inputChannel, MessageQueue messageQueue)48     private static native long nativeInit(WeakReference<InputEventReceiver> receiver,
49             InputChannel inputChannel, MessageQueue messageQueue);
nativeDispose(long receiverPtr)50     private static native void nativeDispose(long receiverPtr);
nativeFinishInputEvent(long receiverPtr, int seq, boolean handled)51     private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
nativeConsumeBatchedInputEvents(long receiverPtr, long frameTimeNanos)52     private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
53             long frameTimeNanos);
54 
55     /**
56      * Creates an input event receiver bound to the specified input channel.
57      *
58      * @param inputChannel The input channel.
59      * @param looper The looper to use when invoking callbacks.
60      */
InputEventReceiver(InputChannel inputChannel, Looper looper)61     public InputEventReceiver(InputChannel inputChannel, Looper looper) {
62         if (inputChannel == null) {
63             throw new IllegalArgumentException("inputChannel must not be null");
64         }
65         if (looper == null) {
66             throw new IllegalArgumentException("looper must not be null");
67         }
68 
69         mInputChannel = inputChannel;
70         mMessageQueue = looper.getQueue();
71         mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
72                 inputChannel, mMessageQueue);
73 
74         mCloseGuard.open("dispose");
75     }
76 
77     @Override
finalize()78     protected void finalize() throws Throwable {
79         try {
80             dispose(true);
81         } finally {
82             super.finalize();
83         }
84     }
85 
86     /**
87      * Disposes the receiver.
88      */
dispose()89     public void dispose() {
90         dispose(false);
91     }
92 
dispose(boolean finalized)93     private void dispose(boolean finalized) {
94         if (mCloseGuard != null) {
95             if (finalized) {
96                 mCloseGuard.warnIfOpen();
97             }
98             mCloseGuard.close();
99         }
100 
101         if (mReceiverPtr != 0) {
102             nativeDispose(mReceiverPtr);
103             mReceiverPtr = 0;
104         }
105         mInputChannel = null;
106         mMessageQueue = null;
107     }
108 
109     /**
110      * Called when an input event is received.
111      * The recipient should process the input event and then call {@link #finishInputEvent}
112      * to indicate whether the event was handled.  No new input events will be received
113      * until {@link #finishInputEvent} is called.
114      *
115      * @param event The input event that was received.
116      */
117     @UnsupportedAppUsage
onInputEvent(InputEvent event)118     public void onInputEvent(InputEvent event) {
119         finishInputEvent(event, false);
120     }
121 
122     /**
123      * Called when a batched input event is pending.
124      *
125      * The batched input event will continue to accumulate additional movement
126      * samples until the recipient calls {@link #consumeBatchedInputEvents} or
127      * an event is received that ends the batch and causes it to be consumed
128      * immediately (such as a pointer up event).
129      */
onBatchedInputEventPending()130     public void onBatchedInputEventPending() {
131         consumeBatchedInputEvents(-1);
132     }
133 
134     /**
135      * Finishes an input event and indicates whether it was handled.
136      * Must be called on the same Looper thread to which the receiver is attached.
137      *
138      * @param event The input event that was finished.
139      * @param handled True if the event was handled.
140      */
finishInputEvent(InputEvent event, boolean handled)141     public final void finishInputEvent(InputEvent event, boolean handled) {
142         if (event == null) {
143             throw new IllegalArgumentException("event must not be null");
144         }
145         if (mReceiverPtr == 0) {
146             Log.w(TAG, "Attempted to finish an input event but the input event "
147                     + "receiver has already been disposed.");
148         } else {
149             int index = mSeqMap.indexOfKey(event.getSequenceNumber());
150             if (index < 0) {
151                 Log.w(TAG, "Attempted to finish an input event that is not in progress.");
152             } else {
153                 int seq = mSeqMap.valueAt(index);
154                 mSeqMap.removeAt(index);
155                 nativeFinishInputEvent(mReceiverPtr, seq, handled);
156             }
157         }
158         event.recycleIfNeededAfterDispatch();
159     }
160 
161     /**
162      * Consumes all pending batched input events.
163      * Must be called on the same Looper thread to which the receiver is attached.
164      *
165      * This method forces all batched input events to be delivered immediately.
166      * Should be called just before animating or drawing a new frame in the UI.
167      *
168      * @param frameTimeNanos The time in the {@link System#nanoTime()} time base
169      * when the current display frame started rendering, or -1 if unknown.
170      *
171      * @return Whether a batch was consumed
172      */
consumeBatchedInputEvents(long frameTimeNanos)173     public final boolean consumeBatchedInputEvents(long frameTimeNanos) {
174         if (mReceiverPtr == 0) {
175             Log.w(TAG, "Attempted to consume batched input events but the input event "
176                     + "receiver has already been disposed.");
177         } else {
178             return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
179         }
180         return false;
181     }
182 
183     // Called from native code.
184     @SuppressWarnings("unused")
185     @UnsupportedAppUsage
dispatchInputEvent(int seq, InputEvent event)186     private void dispatchInputEvent(int seq, InputEvent event) {
187         mSeqMap.put(event.getSequenceNumber(), seq);
188         onInputEvent(event);
189     }
190 
191     // Called from native code.
192     @SuppressWarnings("unused")
193     @UnsupportedAppUsage
dispatchBatchedInputEventPending()194     private void dispatchBatchedInputEventPending() {
195         onBatchedInputEventPending();
196     }
197 
198     public static interface Factory {
createInputEventReceiver( InputChannel inputChannel, Looper looper)199         public InputEventReceiver createInputEventReceiver(
200                 InputChannel inputChannel, Looper looper);
201     }
202 }
203