android_view_InputQueue.cpp revision 4fe6c3e51be77e35f40872cdbca6c80f8f8b7ecb
1d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi/*
2d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi * Copyright (C) 2010 The Android Open Source Project
3d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi *
4d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi * Licensed under the Apache License, Version 2.0 (the "License");
5d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi * you may not use this file except in compliance with the License.
6d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi * You may obtain a copy of the License at
7d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi *
8d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi *      http://www.apache.org/licenses/LICENSE-2.0
9d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi *
10d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi * Unless required by applicable law or agreed to in writing, software
11d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi * distributed under the License is distributed on an "AS IS" BASIS,
12d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi * See the License for the specific language governing permissions and
14d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi * limitations under the License.
15d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi */
16d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi
17d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#define LOG_TAG "InputQueue-JNI"
18d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi
19d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi//#define LOG_NDEBUG 0
20d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi
21d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi// Log debug messages about the dispatch cycle.
22d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#define DEBUG_DISPATCH_CYCLE 0
23d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi
24d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi// Log debug messages about registrations.
25d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#define DEBUG_REGISTRATION 0
26d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi
27d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi
28d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include "JNIHelp.h"
29bd882b1c8708686d373c56e07e6bb8b1cb6ffd9eJeff Brown
30bd882b1c8708686d373c56e07e6bb8b1cb6ffd9eJeff Brown#include <android_runtime/AndroidRuntime.h>
31d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include <utils/Log.h>
32d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include <utils/Looper.h>
33d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include <utils/KeyedVector.h>
34d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include <utils/threads.h>
35d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include <ui/InputTransport.h>
36d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include "android_os_MessageQueue.h"
37d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include "android_view_InputChannel.h"
38d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include "android_view_KeyEvent.h"
39d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi#include "android_view_MotionEvent.h"
403070af08821ee86f06a9cc6b58dbb79c82946b94Iliyan Malchev
41d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshinamespace android {
423802949f5cfe500b8bc41371ff99c82c9ed5d9c3Jean-Baptiste Queru
433802949f5cfe500b8bc41371ff99c82c9ed5d9c3Jean-Baptiste Queru// ----------------------------------------------------------------------------
44d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshi
45d074e30ce44b9e33da43b67a4515b8986ca72b26aimitakeshistatic 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(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(); // 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, bool ignoreSpuriousFinish) {
235    int32_t receiveFd;
236    uint16_t connectionId;
237    uint16_t messageSeqNum;
238    parseFinishedToken(finishedToken, &receiveFd, &connectionId, &messageSeqNum);
239
240    { // acquire lock
241        AutoMutex _l(mLock);
242
243        ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
244        if (connectionIndex < 0) {
245            if (! ignoreSpuriousFinish) {
246                LOGI("Ignoring finish signal on channel that is no longer registered.");
247            }
248            return DEAD_OBJECT;
249        }
250
251        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
252        if (connectionId != connection->id) {
253            if (! ignoreSpuriousFinish) {
254                LOGI("Ignoring finish signal on channel that is no longer registered.");
255            }
256            return DEAD_OBJECT;
257        }
258
259        if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) {
260            if (! ignoreSpuriousFinish) {
261                LOGW("Attempted to finish input twice on channel '%s'.  "
262                        "finished messageSeqNum=%d, current messageSeqNum=%d, messageInProgress=%d",
263                        connection->getInputChannelName(),
264                        messageSeqNum, connection->messageSeqNum, connection->messageInProgress);
265            }
266            return INVALID_OPERATION;
267        }
268
269        connection->messageInProgress = false;
270
271        status_t status = connection->inputConsumer.sendFinishedSignal();
272        if (status) {
273            LOGW("Failed to send finished signal on channel '%s'.  status=%d",
274                    connection->getInputChannelName(), status);
275            return status;
276        }
277
278#if DEBUG_DISPATCH_CYCLE
279        LOGD("channel '%s' ~ Finished event.",
280                connection->getInputChannelName());
281#endif
282    } // release lock
283
284    return OK;
285}
286
287void NativeInputQueue::handleInputChannelDisposed(JNIEnv* env,
288        jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) {
289    LOGW("Input channel object '%s' was disposed without first being unregistered with "
290            "the input queue!", inputChannel->getName().string());
291
292    NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
293    q->unregisterInputChannel(env, inputChannelObj);
294}
295
296int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
297    NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
298    JNIEnv* env = AndroidRuntime::getJNIEnv();
299
300    sp<Connection> connection;
301    InputEvent* inputEvent;
302    jobject inputHandlerObjLocal;
303    jlong finishedToken;
304    { // acquire lock
305        AutoMutex _l(q->mLock);
306
307        ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd);
308        if (connectionIndex < 0) {
309            LOGE("Received spurious receive callback for unknown input channel.  "
310                    "fd=%d, events=0x%x", receiveFd, events);
311            return 0; // remove the callback
312        }
313
314        connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex);
315        if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
316            LOGE("channel '%s' ~ Publisher closed input channel or an error occurred.  "
317                    "events=0x%x", connection->getInputChannelName(), events);
318            return 0; // remove the callback
319        }
320
321        if (! (events & ALOOPER_EVENT_INPUT)) {
322            LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
323                    "events=0x%x", connection->getInputChannelName(), events);
324            return 1;
325        }
326
327        status_t status = connection->inputConsumer.receiveDispatchSignal();
328        if (status) {
329            LOGE("channel '%s' ~ Failed to receive dispatch signal.  status=%d",
330                    connection->getInputChannelName(), status);
331            return 0; // remove the callback
332        }
333
334        if (connection->messageInProgress) {
335            LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.",
336                    connection->getInputChannelName());
337            return 1;
338        }
339
340        status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);
341        if (status) {
342            LOGW("channel '%s' ~ Failed to consume input event.  status=%d",
343                    connection->getInputChannelName(), status);
344            connection->inputConsumer.sendFinishedSignal();
345            return 1;
346        }
347
348        connection->messageInProgress = true;
349        connection->messageSeqNum += 1;
350
351        finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum);
352
353        inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal);
354    } // release lock
355
356    // Invoke the handler outside of the lock.
357    //
358    // Note: inputEvent is stored in a field of the connection object which could potentially
359    //       become disposed due to the input channel being unregistered concurrently.
360    //       For this reason, we explicitly keep the connection object alive by holding
361    //       a strong pointer to it within this scope.  We also grabbed a local reference to
362    //       the input handler object itself for the same reason.
363
364    int32_t inputEventType = inputEvent->getType();
365
366    jobject inputEventObj;
367    jmethodID dispatchMethodId;
368    switch (inputEventType) {
369    case AINPUT_EVENT_TYPE_KEY:
370#if DEBUG_DISPATCH_CYCLE
371        LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName());
372#endif
373        inputEventObj = android_view_KeyEvent_fromNative(env,
374                static_cast<KeyEvent*>(inputEvent));
375        dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent;
376        break;
377
378    case AINPUT_EVENT_TYPE_MOTION:
379#if DEBUG_DISPATCH_CYCLE
380        LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName());
381#endif
382        inputEventObj = android_view_MotionEvent_fromNative(env,
383                static_cast<MotionEvent*>(inputEvent));
384        dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent;
385        break;
386
387    default:
388        assert(false); // InputConsumer should prevent this from ever happening
389        inputEventObj = NULL;
390    }
391
392    if (! inputEventObj) {
393        LOGW("channel '%s' ~ Failed to obtain DVM event object.",
394                connection->getInputChannelName());
395        env->DeleteLocalRef(inputHandlerObjLocal);
396        q->finished(env, finishedToken, false);
397        return 1;
398    }
399
400#if DEBUG_DISPATCH_CYCLE
401    LOGD("Invoking input handler.");
402#endif
403    env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
404            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
405            jlong(finishedToken));
406#if DEBUG_DISPATCH_CYCLE
407    LOGD("Returned from input handler.");
408#endif
409
410    if (env->ExceptionCheck()) {
411        LOGE("An exception occurred while invoking the input handler for an event.");
412        LOGE_EX(env);
413        env->ExceptionClear();
414
415        q->finished(env, finishedToken, true /*ignoreSpuriousFinish*/);
416    }
417
418    env->DeleteLocalRef(inputEventObj);
419    env->DeleteLocalRef(inputHandlerObjLocal);
420    return 1;
421}
422
423jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId,
424        uint16_t messageSeqNum) {
425    return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum);
426}
427
428void NativeInputQueue::parseFinishedToken(jlong finishedToken,
429        int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex) {
430    *outReceiveFd = int32_t(finishedToken >> 32);
431    *outConnectionId = uint16_t(finishedToken >> 16);
432    *outMessageIndex = uint16_t(finishedToken);
433}
434
435// ----------------------------------------------------------------------------
436
437NativeInputQueue::Connection::Connection(uint16_t id,
438        const sp<InputChannel>& inputChannel, const sp<Looper>& looper) :
439    id(id), status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel),
440    looper(looper), inputHandlerObjGlobal(NULL),
441    messageSeqNum(0), messageInProgress(false) {
442}
443
444NativeInputQueue::Connection::~Connection() {
445}
446
447// ----------------------------------------------------------------------------
448
449static NativeInputQueue gNativeInputQueue;
450
451static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
452        jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) {
453    status_t status = gNativeInputQueue.registerInputChannel(
454            env, inputChannelObj, inputHandlerObj, messageQueueObj);
455
456    if (status) {
457        jniThrowRuntimeException(env, "Failed to register input channel.  "
458                "Check logs for details.");
459    }
460}
461
462static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
463        jobject inputChannelObj) {
464    status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj);
465
466    if (status) {
467        jniThrowRuntimeException(env, "Failed to unregister input channel.  "
468                "Check logs for details.");
469    }
470}
471
472static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz,
473        jlong finishedToken) {
474    status_t status = gNativeInputQueue.finished(
475            env, finishedToken, false /*ignoreSpuriousFinish*/);
476
477    // We ignore the case where an event could not be finished because the input channel
478    // was no longer registered (DEAD_OBJECT) since it is a common race that can occur
479    // during application shutdown.  The input dispatcher recovers gracefully anyways.
480    if (status != OK && status != DEAD_OBJECT) {
481        jniThrowRuntimeException(env, "Failed to finish input event.  "
482                "Check logs for details.");
483    }
484}
485
486// ----------------------------------------------------------------------------
487
488static JNINativeMethod gInputQueueMethods[] = {
489    /* name, signature, funcPtr */
490    { "nativeRegisterInputChannel",
491            "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V",
492            (void*)android_view_InputQueue_nativeRegisterInputChannel },
493    { "nativeUnregisterInputChannel",
494            "(Landroid/view/InputChannel;)V",
495            (void*)android_view_InputQueue_nativeUnregisterInputChannel },
496    { "nativeFinished", "(J)V",
497            (void*)android_view_InputQueue_nativeFinished }
498};
499
500#define FIND_CLASS(var, className) \
501        var = env->FindClass(className); \
502        LOG_FATAL_IF(! var, "Unable to find class " className); \
503        var = jclass(env->NewGlobalRef(var));
504
505#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
506        var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
507        LOG_FATAL_IF(! var, "Unable to find static method " methodName);
508
509int register_android_view_InputQueue(JNIEnv* env) {
510    int res = jniRegisterNativeMethods(env, "android/view/InputQueue",
511            gInputQueueMethods, NELEM(gInputQueueMethods));
512    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
513
514    FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue");
515
516    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz,
517            "dispatchKeyEvent",
518            "(Landroid/view/InputHandler;Landroid/view/KeyEvent;J)V");
519
520    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz,
521            "dispatchMotionEvent",
522            "(Landroid/view/InputHandler;Landroid/view/MotionEvent;J)V");
523    return 0;
524}
525
526} // namespace android
527