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
26/**
27 * Provides a low-level mechanism for an application to receive input events.
28 * @hide
29 */
30public 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