1/*
2 * Copyright (C) 2010 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.MessageQueue;
20import android.util.Slog;
21
22/**
23 * An input queue provides a mechanism for an application to receive incoming
24 * input events.  Currently only usable from native code.
25 */
26public final class InputQueue {
27    private static final String TAG = "InputQueue";
28
29    private static final boolean DEBUG = false;
30
31    /**
32     * Interface to receive notification of when an InputQueue is associated
33     * and dissociated with a thread.
34     */
35    public static interface Callback {
36        /**
37         * Called when the given InputQueue is now associated with the
38         * thread making this call, so it can start receiving events from it.
39         */
40        void onInputQueueCreated(InputQueue queue);
41
42        /**
43         * Called when the given InputQueue is no longer associated with
44         * the thread and thus not dispatching events.
45         */
46        void onInputQueueDestroyed(InputQueue queue);
47    }
48
49    final InputChannel mChannel;
50
51    private static final Object sLock = new Object();
52
53    private static native void nativeRegisterInputChannel(InputChannel inputChannel,
54            InputHandler inputHandler, MessageQueue messageQueue);
55    private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
56    private static native void nativeFinished(long finishedToken);
57
58    /** @hide */
59    public InputQueue(InputChannel channel) {
60        mChannel = channel;
61    }
62
63    /** @hide */
64    public InputChannel getInputChannel() {
65        return mChannel;
66    }
67
68    /**
69     * Registers an input channel and handler.
70     * @param inputChannel The input channel to register.
71     * @param inputHandler The input handler to input events send to the target.
72     * @param messageQueue The message queue on whose thread the handler should be invoked.
73     * @hide
74     */
75    public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler,
76            MessageQueue messageQueue) {
77        if (inputChannel == null) {
78            throw new IllegalArgumentException("inputChannel must not be null");
79        }
80        if (inputHandler == null) {
81            throw new IllegalArgumentException("inputHandler must not be null");
82        }
83        if (messageQueue == null) {
84            throw new IllegalArgumentException("messageQueue must not be null");
85        }
86
87        synchronized (sLock) {
88            if (DEBUG) {
89                Slog.d(TAG, "Registering input channel '" + inputChannel + "'");
90            }
91
92            nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue);
93        }
94    }
95
96    /**
97     * Unregisters an input channel.
98     * Does nothing if the channel is not currently registered.
99     * @param inputChannel The input channel to unregister.
100     * @hide
101     */
102    public static void unregisterInputChannel(InputChannel inputChannel) {
103        if (inputChannel == null) {
104            throw new IllegalArgumentException("inputChannel must not be null");
105        }
106
107        synchronized (sLock) {
108            if (DEBUG) {
109                Slog.d(TAG, "Unregistering input channel '" + inputChannel + "'");
110            }
111
112            nativeUnregisterInputChannel(inputChannel);
113        }
114    }
115
116    @SuppressWarnings("unused")
117    private static void dispatchKeyEvent(InputHandler inputHandler,
118            KeyEvent event, long finishedToken) {
119        Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
120        inputHandler.handleKey(event, finishedCallback);
121    }
122
123    @SuppressWarnings("unused")
124    private static void dispatchMotionEvent(InputHandler inputHandler,
125            MotionEvent event, long finishedToken) {
126        Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
127        inputHandler.handleMotion(event, finishedCallback);
128    }
129
130    private static class FinishedCallback implements Runnable {
131        private static final boolean DEBUG_RECYCLING = false;
132
133        private static final int RECYCLE_MAX_COUNT = 4;
134
135        private static FinishedCallback sRecycleHead;
136        private static int sRecycleCount;
137
138        private FinishedCallback mRecycleNext;
139        private long mFinishedToken;
140
141        private FinishedCallback() {
142        }
143
144        public static FinishedCallback obtain(long finishedToken) {
145            synchronized (sLock) {
146                FinishedCallback callback = sRecycleHead;
147                if (callback != null) {
148                    sRecycleHead = callback.mRecycleNext;
149                    sRecycleCount -= 1;
150                    callback.mRecycleNext = null;
151                } else {
152                    callback = new FinishedCallback();
153                }
154                callback.mFinishedToken = finishedToken;
155                return callback;
156            }
157        }
158
159        public void run() {
160            synchronized (sLock) {
161                if (mFinishedToken == -1) {
162                    throw new IllegalStateException("Event finished callback already invoked.");
163                }
164
165                nativeFinished(mFinishedToken);
166                mFinishedToken = -1;
167
168                if (sRecycleCount < RECYCLE_MAX_COUNT) {
169                    mRecycleNext = sRecycleHead;
170                    sRecycleHead = this;
171                    sRecycleCount += 1;
172
173                    if (DEBUG_RECYCLING) {
174                        Slog.d(TAG, "Recycled finished callbacks: " + sRecycleCount);
175                    }
176                }
177            }
178        }
179    }
180}
181