Home | History | Annotate | Download | only in jni
      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 
     41 namespace android {
     42 
     43 // ----------------------------------------------------------------------------
     44 
     45 static struct {
     46     jclass clazz;
     47 
     48     jmethodID dispatchKeyEvent;
     49     jmethodID dispatchMotionEvent;
     50 } gInputQueueClassInfo;
     51 
     52 // ----------------------------------------------------------------------------
     53 
     54 class NativeInputQueue {
     55 public:
     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 
     66 private:
     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 
    124 NativeInputQueue::NativeInputQueue() :
    125         mNextConnectionId(0) {
    126 }
    127 
    128 NativeInputQueue::~NativeInputQueue() {
    129 }
    130 
    131 status_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 
    177 status_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 
    222 ssize_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 
    234 status_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 
    287 void 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 
    296 int 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 
    423 jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId,
    424         uint16_t messageSeqNum) {
    425     return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum);
    426 }
    427 
    428 void 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 
    437 NativeInputQueue::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 
    444 NativeInputQueue::Connection::~Connection() {
    445 }
    446 
    447 // ----------------------------------------------------------------------------
    448 
    449 static NativeInputQueue gNativeInputQueue;
    450 
    451 static 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 
    462 static 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 
    472 static 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 
    488 static 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 
    509 int 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