• 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 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