• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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