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 android.os.Looper;
20import android.os.MessageQueue;
21import android.util.Log;
22import android.util.SparseIntArray;
23
24import dalvik.system.CloseGuard;
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 displayId The display id on which input event triggered.
115     * @param event The input event that was received.
116     */
117    public void onInputEvent(InputEvent event, int displayId) {
118        finishInputEvent(event, false);
119    }
120
121    /**
122     * Called when a batched input event is pending.
123     *
124     * The batched input event will continue to accumulate additional movement
125     * samples until the recipient calls {@link #consumeBatchedInputEvents} or
126     * an event is received that ends the batch and causes it to be consumed
127     * immediately (such as a pointer up event).
128     */
129    public void onBatchedInputEventPending() {
130        consumeBatchedInputEvents(-1);
131    }
132
133    /**
134     * Finishes an input event and indicates whether it was handled.
135     * Must be called on the same Looper thread to which the receiver is attached.
136     *
137     * @param event The input event that was finished.
138     * @param handled True if the event was handled.
139     */
140    public final void finishInputEvent(InputEvent event, boolean handled) {
141        if (event == null) {
142            throw new IllegalArgumentException("event must not be null");
143        }
144        if (mReceiverPtr == 0) {
145            Log.w(TAG, "Attempted to finish an input event but the input event "
146                    + "receiver has already been disposed.");
147        } else {
148            int index = mSeqMap.indexOfKey(event.getSequenceNumber());
149            if (index < 0) {
150                Log.w(TAG, "Attempted to finish an input event that is not in progress.");
151            } else {
152                int seq = mSeqMap.valueAt(index);
153                mSeqMap.removeAt(index);
154                nativeFinishInputEvent(mReceiverPtr, seq, handled);
155            }
156        }
157        event.recycleIfNeededAfterDispatch();
158    }
159
160    /**
161     * Consumes all pending batched input events.
162     * Must be called on the same Looper thread to which the receiver is attached.
163     *
164     * This method forces all batched input events to be delivered immediately.
165     * Should be called just before animating or drawing a new frame in the UI.
166     *
167     * @param frameTimeNanos The time in the {@link System#nanoTime()} time base
168     * when the current display frame started rendering, or -1 if unknown.
169     *
170     * @return Whether a batch was consumed
171     */
172    public final boolean consumeBatchedInputEvents(long frameTimeNanos) {
173        if (mReceiverPtr == 0) {
174            Log.w(TAG, "Attempted to consume batched input events but the input event "
175                    + "receiver has already been disposed.");
176        } else {
177            return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
178        }
179        return false;
180    }
181
182    // Called from native code.
183    @SuppressWarnings("unused")
184    private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
185        mSeqMap.put(event.getSequenceNumber(), seq);
186        onInputEvent(event, displayId);
187    }
188
189    // Called from native code.
190    @SuppressWarnings("unused")
191    private void dispatchBatchedInputEventPending() {
192        onBatchedInputEventPending();
193    }
194
195    public static interface Factory {
196        public InputEventReceiver createInputEventReceiver(
197                InputChannel inputChannel, Looper looper);
198    }
199}
200