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
getInputChannelName() const82 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
NativeInputQueue()124 NativeInputQueue::NativeInputQueue() :
125 mNextConnectionId(0) {
126 }
127
~NativeInputQueue()128 NativeInputQueue::~NativeInputQueue() {
129 }
130
registerInputChannel(JNIEnv * env,jobject inputChannelObj,jobject inputHandlerObj,jobject messageQueueObj)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
unregisterInputChannel(JNIEnv * env,jobject inputChannelObj)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
getConnectionIndex(const sp<InputChannel> & inputChannel)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
finished(JNIEnv * env,jlong finishedToken,bool ignoreSpuriousFinish)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
handleInputChannelDisposed(JNIEnv * env,jobject inputChannelObj,const sp<InputChannel> & inputChannel,void * data)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
handleReceiveCallback(int receiveFd,int events,void * data)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
generateFinishedToken(int32_t receiveFd,uint16_t connectionId,uint16_t messageSeqNum)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
parseFinishedToken(jlong finishedToken,int32_t * outReceiveFd,uint16_t * outConnectionId,uint16_t * outMessageIndex)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
Connection(uint16_t id,const sp<InputChannel> & inputChannel,const sp<Looper> & looper)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
~Connection()444 NativeInputQueue::Connection::~Connection() {
445 }
446
447 // ----------------------------------------------------------------------------
448
449 static NativeInputQueue gNativeInputQueue;
450
android_view_InputQueue_nativeRegisterInputChannel(JNIEnv * env,jclass clazz,jobject inputChannelObj,jobject inputHandlerObj,jobject messageQueueObj)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
android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv * env,jclass clazz,jobject inputChannelObj)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
android_view_InputQueue_nativeFinished(JNIEnv * env,jclass clazz,jlong finishedToken)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
register_android_view_InputQueue(JNIEnv * env)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