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