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