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