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