android_view_InputQueue.cpp revision 9c3cda04d969912bc46184f2b326d1db95e0aba5
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
17#define LOG_TAG "InputQueue-JNI"
18
19//#define LOG_NDEBUG 0
20
21// Log debug messages about the dispatch cycle.
22#define DEBUG_DISPATCH_CYCLE 1
23
24// Log debug messages about registrations.
25#define DEBUG_REGISTRATION 1
26
27
28#include "JNIHelp.h"
29
30#include <android_runtime/AndroidRuntime.h>
31#include <utils/Log.h>
32#include <utils/PollLoop.h>
33#include <utils/KeyedVector.h>
34#include <utils/threads.h>
35#include <ui/InputTransport.h>
36#include "android_os_MessageQueue.h"
37#include "android_view_InputChannel.h"
38#include "android_view_KeyEvent.h"
39#include "android_view_MotionEvent.h"
40
41namespace android {
42
43// ----------------------------------------------------------------------------
44
45static struct {
46    jclass clazz;
47
48    jmethodID dispatchKeyEvent;
49    jmethodID dispatchMotionEvent;
50} gInputQueueClassInfo;
51
52// ----------------------------------------------------------------------------
53
54class NativeInputQueue {
55public:
56    NativeInputQueue();
57    ~NativeInputQueue();
58
59    status_t registerInputChannel(JNIEnv* env, jobject inputChannelObj,
60            jobject inputHandlerObj, jobject messageQueueObj);
61
62    status_t unregisterInputChannel(JNIEnv* env, jobject inputChannelObj);
63
64    status_t finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish);
65
66private:
67    class Connection : public RefBase {
68    protected:
69        virtual ~Connection();
70
71    public:
72        enum Status {
73            // Everything is peachy.
74            STATUS_NORMAL,
75            // The input channel has been unregistered.
76            STATUS_ZOMBIE
77        };
78
79        Connection(const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop);
80
81        inline const char* getInputChannelName() const { return inputChannel->getName().string(); }
82
83        Status status;
84
85        sp<InputChannel> inputChannel;
86        InputConsumer inputConsumer;
87        sp<PollLoop> pollLoop;
88        jobject inputHandlerObjGlobal;
89        PreallocatedInputEventFactory inputEventFactory;
90
91        // The sequence number of the current event being dispatched.
92        // This is used as part of the finished token as a way to determine whether the finished
93        // token is still valid before sending a finished signal back to the publisher.
94        uint32_t messageSeqNum;
95
96        // True if a message has been received from the publisher but not yet finished.
97        bool messageInProgress;
98    };
99
100    Mutex mLock;
101    KeyedVector<int32_t, sp<Connection> > mConnectionsByReceiveFd;
102
103    static void handleInputChannelDisposed(JNIEnv* env,
104            jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data);
105
106    static bool handleReceiveCallback(int receiveFd, int events, void* data);
107
108    static jlong generateFinishedToken(int32_t receiveFd, int32_t messageSeqNum);
109
110    static void parseFinishedToken(jlong finishedToken,
111            int32_t* outReceiveFd, uint32_t* outMessageIndex);
112};
113
114// ----------------------------------------------------------------------------
115
116NativeInputQueue::NativeInputQueue() {
117}
118
119NativeInputQueue::~NativeInputQueue() {
120}
121
122status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj,
123        jobject inputHandlerObj, jobject messageQueueObj) {
124    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
125            inputChannelObj);
126    if (inputChannel == NULL) {
127        LOGW("Input channel is not initialized.");
128        return BAD_VALUE;
129    }
130
131#if DEBUG_REGISTRATION
132    LOGD("channel '%s' - Registered", inputChannel->getName().string());
133#endif
134
135    sp<PollLoop> pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueueObj);
136
137    int receiveFd;
138    { // acquire lock
139        AutoMutex _l(mLock);
140
141        receiveFd = inputChannel->getReceivePipeFd();
142        if (mConnectionsByReceiveFd.indexOfKey(receiveFd) >= 0) {
143            LOGW("Attempted to register already registered input channel '%s'",
144                    inputChannel->getName().string());
145            return BAD_VALUE;
146        }
147
148        sp<Connection> connection = new Connection(inputChannel, pollLoop);
149        status_t result = connection->inputConsumer.initialize();
150        if (result) {
151            LOGW("Failed to initialize input consumer for input channel '%s', status=%d",
152                    inputChannel->getName().string(), result);
153            return result;
154        }
155
156        connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj);
157
158        mConnectionsByReceiveFd.add(receiveFd, connection);
159    } // release lock
160
161    android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
162            handleInputChannelDisposed, this);
163
164    pollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
165    return OK;
166}
167
168status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) {
169    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
170            inputChannelObj);
171    if (inputChannel == NULL) {
172        LOGW("Input channel is not initialized.");
173        return BAD_VALUE;
174    }
175
176#if DEBUG_REGISTRATION
177    LOGD("channel '%s' - Unregistered", inputChannel->getName().string());
178#endif
179
180    int32_t receiveFd;
181    sp<Connection> connection;
182    { // acquire lock
183        AutoMutex _l(mLock);
184
185        receiveFd = inputChannel->getReceivePipeFd();
186        ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
187        if (connectionIndex < 0) {
188            LOGW("Attempted to unregister already unregistered input channel '%s'",
189                    inputChannel->getName().string());
190            return BAD_VALUE;
191        }
192
193        connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
194        mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
195
196        connection->status = Connection::STATUS_ZOMBIE;
197
198        env->DeleteGlobalRef(connection->inputHandlerObjGlobal);
199        connection->inputHandlerObjGlobal = NULL;
200    } // release lock
201
202    android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL);
203
204    connection->pollLoop->removeCallback(receiveFd);
205    return OK;
206}
207
208status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish) {
209    int32_t receiveFd;
210    uint32_t messageSeqNum;
211    parseFinishedToken(finishedToken, &receiveFd, &messageSeqNum);
212
213    { // acquire lock
214        AutoMutex _l(mLock);
215
216        ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
217        if (connectionIndex < 0) {
218            if (! ignoreSpuriousFinish) {
219                LOGW("Attempted to finish input on channel that is no longer registered.");
220            }
221            return DEAD_OBJECT;
222        }
223
224        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
225        if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) {
226            if (! ignoreSpuriousFinish) {
227                LOGW("Attempted to finish input twice on channel '%s'.",
228                        connection->getInputChannelName());
229            }
230            return INVALID_OPERATION;
231        }
232
233        connection->messageInProgress = false;
234
235        status_t status = connection->inputConsumer.sendFinishedSignal();
236        if (status) {
237            LOGW("Failed to send finished signal on channel '%s'.  status=%d",
238                    connection->getInputChannelName(), status);
239            return status;
240        }
241
242#if DEBUG_DISPATCH_CYCLE
243        LOGD("channel '%s' ~ Finished event.",
244                connection->getInputChannelName());
245#endif
246    } // release lock
247
248    return OK;
249}
250
251void NativeInputQueue::handleInputChannelDisposed(JNIEnv* env,
252        jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) {
253    LOGW("Input channel object '%s' was disposed without first being unregistered with "
254            "the input queue!", inputChannel->getName().string());
255
256    NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
257    q->unregisterInputChannel(env, inputChannelObj);
258}
259
260bool NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
261    NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
262    JNIEnv* env = AndroidRuntime::getJNIEnv();
263
264    sp<Connection> connection;
265    InputEvent* inputEvent;
266    jobject inputHandlerObjLocal;
267    jlong finishedToken;
268    { // acquire lock
269        AutoMutex _l(q->mLock);
270
271        ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd);
272        if (connectionIndex < 0) {
273            LOGE("Received spurious receive callback for unknown input channel.  "
274                    "fd=%d, events=0x%x", receiveFd, events);
275            return false; // remove the callback
276        }
277
278        connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex);
279        if (events & (POLLERR | POLLHUP | POLLNVAL)) {
280            LOGE("channel '%s' ~ Publisher closed input channel or an error occurred.  "
281                    "events=0x%x", connection->getInputChannelName(), events);
282            return false; // remove the callback
283        }
284
285        if (! (events & POLLIN)) {
286            LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
287                    "events=0x%x", connection->getInputChannelName(), events);
288            return true;
289        }
290
291        status_t status = connection->inputConsumer.receiveDispatchSignal();
292        if (status) {
293            LOGE("channel '%s' ~ Failed to receive dispatch signal.  status=%d",
294                    connection->getInputChannelName(), status);
295            return false; // remove the callback
296        }
297
298        if (connection->messageInProgress) {
299            LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.",
300                    connection->getInputChannelName());
301            return true;
302        }
303
304        status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);
305        if (status) {
306            LOGW("channel '%s' ~ Failed to consume input event.  status=%d",
307                    connection->getInputChannelName(), status);
308            connection->inputConsumer.sendFinishedSignal();
309            return true;
310        }
311
312        connection->messageInProgress = true;
313        connection->messageSeqNum += 1;
314
315        finishedToken = generateFinishedToken(receiveFd, connection->messageSeqNum);
316
317        inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal);
318    } // release lock
319
320    // Invoke the handler outside of the lock.
321    //
322    // Note: inputEvent is stored in a field of the connection object which could potentially
323    //       become disposed due to the input channel being unregistered concurrently.
324    //       For this reason, we explicitly keep the connection object alive by holding
325    //       a strong pointer to it within this scope.  We also grabbed a local reference to
326    //       the input handler object itself for the same reason.
327
328    int32_t inputEventType = inputEvent->getType();
329    int32_t inputEventNature = inputEvent->getNature();
330
331    jobject inputEventObj;
332    jmethodID dispatchMethodId;
333    switch (inputEventType) {
334    case INPUT_EVENT_TYPE_KEY:
335#if DEBUG_DISPATCH_CYCLE
336        LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName());
337#endif
338        inputEventObj = android_view_KeyEvent_fromNative(env,
339                static_cast<KeyEvent*>(inputEvent));
340        dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent;
341        break;
342
343    case INPUT_EVENT_TYPE_MOTION:
344#if DEBUG_DISPATCH_CYCLE
345        LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName());
346#endif
347        inputEventObj = android_view_MotionEvent_fromNative(env,
348                static_cast<MotionEvent*>(inputEvent));
349        dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent;
350        break;
351
352    default:
353        assert(false); // InputConsumer should prevent this from ever happening
354        inputEventObj = NULL;
355    }
356
357    if (! inputEventObj) {
358        LOGW("channel '%s' ~ Failed to obtain DVM event object.",
359                connection->getInputChannelName());
360        env->DeleteLocalRef(inputHandlerObjLocal);
361        q->finished(env, finishedToken, false);
362        return true;
363    }
364
365#if DEBUG_DISPATCH_CYCLE
366    LOGD("Invoking input handler.");
367#endif
368    env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
369            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
370            jint(inputEventNature), jlong(finishedToken));
371#if DEBUG_DISPATCH_CYCLE
372    LOGD("Returned from input handler.");
373#endif
374
375    if (env->ExceptionCheck()) {
376        LOGE("An exception occurred while invoking the input handler for an event.");
377        LOGE_EX(env);
378        env->ExceptionClear();
379
380        q->finished(env, finishedToken, true /*ignoreSpuriousFinish*/);
381    }
382
383    env->DeleteLocalRef(inputEventObj);
384    env->DeleteLocalRef(inputHandlerObjLocal);
385    return true;
386}
387
388jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, int32_t messageSeqNum) {
389    return (jlong(receiveFd) << 32) | jlong(messageSeqNum);
390}
391
392void NativeInputQueue::parseFinishedToken(jlong finishedToken,
393        int32_t* outReceiveFd, uint32_t* outMessageIndex) {
394    *outReceiveFd = int32_t(finishedToken >> 32);
395    *outMessageIndex = uint32_t(finishedToken & 0xffffffff);
396}
397
398// ----------------------------------------------------------------------------
399
400NativeInputQueue::Connection::Connection(const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop) :
401    status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel),
402    pollLoop(pollLoop), inputHandlerObjGlobal(NULL),
403    messageSeqNum(0), messageInProgress(false) {
404}
405
406NativeInputQueue::Connection::~Connection() {
407}
408
409// ----------------------------------------------------------------------------
410
411static NativeInputQueue gNativeInputQueue;
412
413static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
414        jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) {
415    status_t status = gNativeInputQueue.registerInputChannel(
416            env, inputChannelObj, inputHandlerObj, messageQueueObj);
417    if (status) {
418        jniThrowRuntimeException(env, "Failed to register input channel.  "
419                "Check logs for details.");
420    }
421}
422
423static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
424        jobject inputChannelObj) {
425    status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj);
426    if (status) {
427        jniThrowRuntimeException(env, "Failed to unregister input channel.  "
428                "Check logs for details.");
429    }
430}
431
432static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz,
433        jlong finishedToken) {
434    status_t status = gNativeInputQueue.finished(
435            env, finishedToken, false /*ignoreSpuriousFinish*/);
436    if (status) {
437        jniThrowRuntimeException(env, "Failed to finish input event.  "
438                "Check logs for details.");
439    }
440}
441
442// ----------------------------------------------------------------------------
443
444static JNINativeMethod gInputQueueMethods[] = {
445    /* name, signature, funcPtr */
446    { "nativeRegisterInputChannel",
447            "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V",
448            (void*)android_view_InputQueue_nativeRegisterInputChannel },
449    { "nativeUnregisterInputChannel",
450            "(Landroid/view/InputChannel;)V",
451            (void*)android_view_InputQueue_nativeUnregisterInputChannel },
452    { "nativeFinished", "(J)V",
453            (void*)android_view_InputQueue_nativeFinished }
454};
455
456#define FIND_CLASS(var, className) \
457        var = env->FindClass(className); \
458        LOG_FATAL_IF(! var, "Unable to find class " className); \
459        var = jclass(env->NewGlobalRef(var));
460
461#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
462        var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
463        LOG_FATAL_IF(! var, "Unable to find static method " methodName);
464
465int register_android_view_InputQueue(JNIEnv* env) {
466    int res = jniRegisterNativeMethods(env, "android/view/InputQueue",
467            gInputQueueMethods, NELEM(gInputQueueMethods));
468    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
469
470    FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue");
471
472    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz,
473            "dispatchKeyEvent",
474            "(Landroid/view/InputHandler;Landroid/view/KeyEvent;IJ)V");
475
476    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz,
477            "dispatchMotionEvent",
478            "(Landroid/view/InputHandler;Landroid/view/MotionEvent;IJ)V");
479    return 0;
480}
481
482} // namespace android
483