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