android_view_InputQueue.cpp revision 9c3cda04d969912bc46184f2b326d1db95e0aba5
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 1 23 24// Log debug messages about registrations. 25#define DEBUG_REGISTRATION 1 26 27 28#include "JNIHelp.h" 29 30#include <android_runtime/AndroidRuntime.h> 31#include <utils/Log.h> 32#include <utils/PollLoop.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 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(const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop); 80 81 inline const char* getInputChannelName() const { return inputChannel->getName().string(); } 82 83 Status status; 84 85 sp<InputChannel> inputChannel; 86 InputConsumer inputConsumer; 87 sp<PollLoop> pollLoop; 88 jobject inputHandlerObjGlobal; 89 PreallocatedInputEventFactory inputEventFactory; 90 91 // The sequence number of the current event being dispatched. 92 // This is used as part of the finished token as a way to determine whether the finished 93 // token is still valid before sending a finished signal back to the publisher. 94 uint32_t messageSeqNum; 95 96 // True if a message has been received from the publisher but not yet finished. 97 bool messageInProgress; 98 }; 99 100 Mutex mLock; 101 KeyedVector<int32_t, sp<Connection> > mConnectionsByReceiveFd; 102 103 static void handleInputChannelDisposed(JNIEnv* env, 104 jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data); 105 106 static bool handleReceiveCallback(int receiveFd, int events, void* data); 107 108 static jlong generateFinishedToken(int32_t receiveFd, int32_t messageSeqNum); 109 110 static void parseFinishedToken(jlong finishedToken, 111 int32_t* outReceiveFd, uint32_t* outMessageIndex); 112}; 113 114// ---------------------------------------------------------------------------- 115 116NativeInputQueue::NativeInputQueue() { 117} 118 119NativeInputQueue::~NativeInputQueue() { 120} 121 122status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj, 123 jobject inputHandlerObj, jobject messageQueueObj) { 124 sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, 125 inputChannelObj); 126 if (inputChannel == NULL) { 127 LOGW("Input channel is not initialized."); 128 return BAD_VALUE; 129 } 130 131#if DEBUG_REGISTRATION 132 LOGD("channel '%s' - Registered", inputChannel->getName().string()); 133#endif 134 135 sp<PollLoop> pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueueObj); 136 137 int receiveFd; 138 { // acquire lock 139 AutoMutex _l(mLock); 140 141 receiveFd = inputChannel->getReceivePipeFd(); 142 if (mConnectionsByReceiveFd.indexOfKey(receiveFd) >= 0) { 143 LOGW("Attempted to register already registered input channel '%s'", 144 inputChannel->getName().string()); 145 return BAD_VALUE; 146 } 147 148 sp<Connection> connection = new Connection(inputChannel, pollLoop); 149 status_t result = connection->inputConsumer.initialize(); 150 if (result) { 151 LOGW("Failed to initialize input consumer for input channel '%s', status=%d", 152 inputChannel->getName().string(), result); 153 return result; 154 } 155 156 connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj); 157 158 mConnectionsByReceiveFd.add(receiveFd, connection); 159 } // release lock 160 161 android_view_InputChannel_setDisposeCallback(env, inputChannelObj, 162 handleInputChannelDisposed, this); 163 164 pollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this); 165 return OK; 166} 167 168status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) { 169 sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, 170 inputChannelObj); 171 if (inputChannel == NULL) { 172 LOGW("Input channel is not initialized."); 173 return BAD_VALUE; 174 } 175 176#if DEBUG_REGISTRATION 177 LOGD("channel '%s' - Unregistered", inputChannel->getName().string()); 178#endif 179 180 int32_t receiveFd; 181 sp<Connection> connection; 182 { // acquire lock 183 AutoMutex _l(mLock); 184 185 receiveFd = inputChannel->getReceivePipeFd(); 186 ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); 187 if (connectionIndex < 0) { 188 LOGW("Attempted to unregister already unregistered input channel '%s'", 189 inputChannel->getName().string()); 190 return BAD_VALUE; 191 } 192 193 connection = mConnectionsByReceiveFd.valueAt(connectionIndex); 194 mConnectionsByReceiveFd.removeItemsAt(connectionIndex); 195 196 connection->status = Connection::STATUS_ZOMBIE; 197 198 env->DeleteGlobalRef(connection->inputHandlerObjGlobal); 199 connection->inputHandlerObjGlobal = NULL; 200 } // release lock 201 202 android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL); 203 204 connection->pollLoop->removeCallback(receiveFd); 205 return OK; 206} 207 208status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish) { 209 int32_t receiveFd; 210 uint32_t messageSeqNum; 211 parseFinishedToken(finishedToken, &receiveFd, &messageSeqNum); 212 213 { // acquire lock 214 AutoMutex _l(mLock); 215 216 ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); 217 if (connectionIndex < 0) { 218 if (! ignoreSpuriousFinish) { 219 LOGW("Attempted to finish input on channel that is no longer registered."); 220 } 221 return DEAD_OBJECT; 222 } 223 224 sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); 225 if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) { 226 if (! ignoreSpuriousFinish) { 227 LOGW("Attempted to finish input twice on channel '%s'.", 228 connection->getInputChannelName()); 229 } 230 return INVALID_OPERATION; 231 } 232 233 connection->messageInProgress = false; 234 235 status_t status = connection->inputConsumer.sendFinishedSignal(); 236 if (status) { 237 LOGW("Failed to send finished signal on channel '%s'. status=%d", 238 connection->getInputChannelName(), status); 239 return status; 240 } 241 242#if DEBUG_DISPATCH_CYCLE 243 LOGD("channel '%s' ~ Finished event.", 244 connection->getInputChannelName()); 245#endif 246 } // release lock 247 248 return OK; 249} 250 251void NativeInputQueue::handleInputChannelDisposed(JNIEnv* env, 252 jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) { 253 LOGW("Input channel object '%s' was disposed without first being unregistered with " 254 "the input queue!", inputChannel->getName().string()); 255 256 NativeInputQueue* q = static_cast<NativeInputQueue*>(data); 257 q->unregisterInputChannel(env, inputChannelObj); 258} 259 260bool NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) { 261 NativeInputQueue* q = static_cast<NativeInputQueue*>(data); 262 JNIEnv* env = AndroidRuntime::getJNIEnv(); 263 264 sp<Connection> connection; 265 InputEvent* inputEvent; 266 jobject inputHandlerObjLocal; 267 jlong finishedToken; 268 { // acquire lock 269 AutoMutex _l(q->mLock); 270 271 ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd); 272 if (connectionIndex < 0) { 273 LOGE("Received spurious receive callback for unknown input channel. " 274 "fd=%d, events=0x%x", receiveFd, events); 275 return false; // remove the callback 276 } 277 278 connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex); 279 if (events & (POLLERR | POLLHUP | POLLNVAL)) { 280 LOGE("channel '%s' ~ Publisher closed input channel or an error occurred. " 281 "events=0x%x", connection->getInputChannelName(), events); 282 return false; // remove the callback 283 } 284 285 if (! (events & POLLIN)) { 286 LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " 287 "events=0x%x", connection->getInputChannelName(), events); 288 return true; 289 } 290 291 status_t status = connection->inputConsumer.receiveDispatchSignal(); 292 if (status) { 293 LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", 294 connection->getInputChannelName(), status); 295 return false; // remove the callback 296 } 297 298 if (connection->messageInProgress) { 299 LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.", 300 connection->getInputChannelName()); 301 return true; 302 } 303 304 status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent); 305 if (status) { 306 LOGW("channel '%s' ~ Failed to consume input event. status=%d", 307 connection->getInputChannelName(), status); 308 connection->inputConsumer.sendFinishedSignal(); 309 return true; 310 } 311 312 connection->messageInProgress = true; 313 connection->messageSeqNum += 1; 314 315 finishedToken = generateFinishedToken(receiveFd, connection->messageSeqNum); 316 317 inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal); 318 } // release lock 319 320 // Invoke the handler outside of the lock. 321 // 322 // Note: inputEvent is stored in a field of the connection object which could potentially 323 // become disposed due to the input channel being unregistered concurrently. 324 // For this reason, we explicitly keep the connection object alive by holding 325 // a strong pointer to it within this scope. We also grabbed a local reference to 326 // the input handler object itself for the same reason. 327 328 int32_t inputEventType = inputEvent->getType(); 329 int32_t inputEventNature = inputEvent->getNature(); 330 331 jobject inputEventObj; 332 jmethodID dispatchMethodId; 333 switch (inputEventType) { 334 case INPUT_EVENT_TYPE_KEY: 335#if DEBUG_DISPATCH_CYCLE 336 LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName()); 337#endif 338 inputEventObj = android_view_KeyEvent_fromNative(env, 339 static_cast<KeyEvent*>(inputEvent)); 340 dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent; 341 break; 342 343 case INPUT_EVENT_TYPE_MOTION: 344#if DEBUG_DISPATCH_CYCLE 345 LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName()); 346#endif 347 inputEventObj = android_view_MotionEvent_fromNative(env, 348 static_cast<MotionEvent*>(inputEvent)); 349 dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent; 350 break; 351 352 default: 353 assert(false); // InputConsumer should prevent this from ever happening 354 inputEventObj = NULL; 355 } 356 357 if (! inputEventObj) { 358 LOGW("channel '%s' ~ Failed to obtain DVM event object.", 359 connection->getInputChannelName()); 360 env->DeleteLocalRef(inputHandlerObjLocal); 361 q->finished(env, finishedToken, false); 362 return true; 363 } 364 365#if DEBUG_DISPATCH_CYCLE 366 LOGD("Invoking input handler."); 367#endif 368 env->CallStaticVoidMethod(gInputQueueClassInfo.clazz, 369 dispatchMethodId, inputHandlerObjLocal, inputEventObj, 370 jint(inputEventNature), jlong(finishedToken)); 371#if DEBUG_DISPATCH_CYCLE 372 LOGD("Returned from input handler."); 373#endif 374 375 if (env->ExceptionCheck()) { 376 LOGE("An exception occurred while invoking the input handler for an event."); 377 LOGE_EX(env); 378 env->ExceptionClear(); 379 380 q->finished(env, finishedToken, true /*ignoreSpuriousFinish*/); 381 } 382 383 env->DeleteLocalRef(inputEventObj); 384 env->DeleteLocalRef(inputHandlerObjLocal); 385 return true; 386} 387 388jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, int32_t messageSeqNum) { 389 return (jlong(receiveFd) << 32) | jlong(messageSeqNum); 390} 391 392void NativeInputQueue::parseFinishedToken(jlong finishedToken, 393 int32_t* outReceiveFd, uint32_t* outMessageIndex) { 394 *outReceiveFd = int32_t(finishedToken >> 32); 395 *outMessageIndex = uint32_t(finishedToken & 0xffffffff); 396} 397 398// ---------------------------------------------------------------------------- 399 400NativeInputQueue::Connection::Connection(const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop) : 401 status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel), 402 pollLoop(pollLoop), inputHandlerObjGlobal(NULL), 403 messageSeqNum(0), messageInProgress(false) { 404} 405 406NativeInputQueue::Connection::~Connection() { 407} 408 409// ---------------------------------------------------------------------------- 410 411static NativeInputQueue gNativeInputQueue; 412 413static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, 414 jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) { 415 status_t status = gNativeInputQueue.registerInputChannel( 416 env, inputChannelObj, inputHandlerObj, messageQueueObj); 417 if (status) { 418 jniThrowRuntimeException(env, "Failed to register input channel. " 419 "Check logs for details."); 420 } 421} 422 423static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, 424 jobject inputChannelObj) { 425 status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj); 426 if (status) { 427 jniThrowRuntimeException(env, "Failed to unregister input channel. " 428 "Check logs for details."); 429 } 430} 431 432static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz, 433 jlong finishedToken) { 434 status_t status = gNativeInputQueue.finished( 435 env, finishedToken, false /*ignoreSpuriousFinish*/); 436 if (status) { 437 jniThrowRuntimeException(env, "Failed to finish input event. " 438 "Check logs for details."); 439 } 440} 441 442// ---------------------------------------------------------------------------- 443 444static JNINativeMethod gInputQueueMethods[] = { 445 /* name, signature, funcPtr */ 446 { "nativeRegisterInputChannel", 447 "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V", 448 (void*)android_view_InputQueue_nativeRegisterInputChannel }, 449 { "nativeUnregisterInputChannel", 450 "(Landroid/view/InputChannel;)V", 451 (void*)android_view_InputQueue_nativeUnregisterInputChannel }, 452 { "nativeFinished", "(J)V", 453 (void*)android_view_InputQueue_nativeFinished } 454}; 455 456#define FIND_CLASS(var, className) \ 457 var = env->FindClass(className); \ 458 LOG_FATAL_IF(! var, "Unable to find class " className); \ 459 var = jclass(env->NewGlobalRef(var)); 460 461#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \ 462 var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \ 463 LOG_FATAL_IF(! var, "Unable to find static method " methodName); 464 465int register_android_view_InputQueue(JNIEnv* env) { 466 int res = jniRegisterNativeMethods(env, "android/view/InputQueue", 467 gInputQueueMethods, NELEM(gInputQueueMethods)); 468 LOG_FATAL_IF(res < 0, "Unable to register native methods."); 469 470 FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue"); 471 472 GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz, 473 "dispatchKeyEvent", 474 "(Landroid/view/InputHandler;Landroid/view/KeyEvent;IJ)V"); 475 476 GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz, 477 "dispatchMotionEvent", 478 "(Landroid/view/InputHandler;Landroid/view/MotionEvent;IJ)V"); 479 return 0; 480} 481 482} // namespace android 483