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
17package android.view;
18
19import dalvik.system.CloseGuard;
20
21import android.os.Looper;
22import android.os.MessageQueue;
23import android.util.Log;
24import android.util.SparseIntArray;
25
26import java.lang.ref.WeakReference;
27
28/**
29 * Provides a low-level mechanism for an application to receive input events.
30 * @hide
31 */
32public 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
47    private static native long nativeInit(WeakReference<InputEventReceiver> receiver,
48            InputChannel inputChannel, MessageQueue messageQueue);
49    private static native void nativeDispose(long receiverPtr);
50    private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
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     */
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
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     */
88    public void dispose() {
89        dispose(false);
90    }
91
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     */
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     */
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     */
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     */
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")
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")
190    private void dispatchBatchedInputEventPending() {
191        onBatchedInputEventPending();
192    }
193
194    public static interface Factory {
195        public InputEventReceiver createInputEventReceiver(
196                InputChannel inputChannel, Looper looper);
197    }
198}
199