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
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(false); // 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 handled,bool ignoreSpuriousFinish)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
handleInputChannelDisposed(JNIEnv * env,jobject inputChannelObj,const sp<InputChannel> & inputChannel,void * data)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
handleReceiveCallback(int receiveFd,int events,void * data)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
generateFinishedToken(int32_t receiveFd,uint16_t connectionId,uint16_t messageSeqNum)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
parseFinishedToken(jlong finishedToken,int32_t * outReceiveFd,uint16_t * outConnectionId,uint16_t * outMessageIndex)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
Connection(uint16_t id,const sp<InputChannel> & inputChannel,const sp<Looper> & looper)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
~Connection()445 NativeInputQueue::Connection::~Connection() {
446 }
447
448 // ----------------------------------------------------------------------------
449
450 static NativeInputQueue gNativeInputQueue;
451
android_view_InputQueue_nativeRegisterInputChannel(JNIEnv * env,jclass clazz,jobject inputChannelObj,jobject inputHandlerObj,jobject messageQueueObj)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
android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv * env,jclass clazz,jobject inputChannelObj)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
android_view_InputQueue_nativeFinished(JNIEnv * env,jclass clazz,jlong finishedToken,bool handled)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
register_android_view_InputQueue(JNIEnv * env)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