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 handled, 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(false); // 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,
    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 
    288 void 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 
    297 int 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 
    424 jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId,
    425         uint16_t messageSeqNum) {
    426     return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum);
    427 }
    428 
    429 void 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 
    438 NativeInputQueue::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 
    445 NativeInputQueue::Connection::~Connection() {
    446 }
    447 
    448 // ----------------------------------------------------------------------------
    449 
    450 static NativeInputQueue gNativeInputQueue;
    451 
    452 static 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         String8 message;
    459         message.appendFormat("Failed to register input channel.  status=%d", status);
    460         jniThrowRuntimeException(env, message.string());
    461     }
    462 }
    463 
    464 static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
    465         jobject inputChannelObj) {
    466     status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj);
    467 
    468     if (status) {
    469         String8 message;
    470         message.appendFormat("Failed to unregister input channel.  status=%d", status);
    471         jniThrowRuntimeException(env, message.string());
    472     }
    473 }
    474 
    475 static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz,
    476         jlong finishedToken, bool handled) {
    477     status_t status = gNativeInputQueue.finished(
    478             env, finishedToken, handled, false /*ignoreSpuriousFinish*/);
    479 
    480     // We ignore the case where an event could not be finished because the input channel
    481     // was no longer registered (DEAD_OBJECT) since it is a common race that can occur
    482     // during application shutdown.  The input dispatcher recovers gracefully anyways.
    483     if (status != OK && status != DEAD_OBJECT) {
    484         String8 message;
    485         message.appendFormat("Failed to finish input event.  status=%d", status);
    486         jniThrowRuntimeException(env, message.string());
    487     }
    488 }
    489 
    490 // ----------------------------------------------------------------------------
    491 
    492 static JNINativeMethod gInputQueueMethods[] = {
    493     /* name, signature, funcPtr */
    494     { "nativeRegisterInputChannel",
    495             "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V",
    496             (void*)android_view_InputQueue_nativeRegisterInputChannel },
    497     { "nativeUnregisterInputChannel",
    498             "(Landroid/view/InputChannel;)V",
    499             (void*)android_view_InputQueue_nativeUnregisterInputChannel },
    500     { "nativeFinished", "(JZ)V",
    501             (void*)android_view_InputQueue_nativeFinished }
    502 };
    503 
    504 #define FIND_CLASS(var, className) \
    505         var = env->FindClass(className); \
    506         LOG_FATAL_IF(! var, "Unable to find class " className); \
    507         var = jclass(env->NewGlobalRef(var));
    508 
    509 #define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
    510         var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
    511         LOG_FATAL_IF(! var, "Unable to find static method " methodName);
    512 
    513 int register_android_view_InputQueue(JNIEnv* env) {
    514     int res = jniRegisterNativeMethods(env, "android/view/InputQueue",
    515             gInputQueueMethods, NELEM(gInputQueueMethods));
    516     LOG_FATAL_IF(res < 0, "Unable to register native methods.");
    517 
    518     FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue");
    519 
    520     GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz,
    521             "dispatchKeyEvent",
    522             "(Landroid/view/InputHandler;Landroid/view/KeyEvent;J)V");
    523 
    524     GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz,
    525             "dispatchMotionEvent",
    526             "(Landroid/view/InputHandler;Landroid/view/MotionEvent;J)V");
    527     return 0;
    528 }
    529 
    530 } // namespace android
    531