From 2c6081ce3593712f30dacd990a97209c791d6ced Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Thu, 15 Jul 2010 17:44:53 -0700 Subject: [PATCH] Implement native key pre-dispatching to IMEs. This significantly re-works the native key dispatching code to allow events to be pre-dispatched to the current IME before being processed by native code. It introduces one new public API, which must be called after retrieving an event if the app wishes for it to be pre-dispatched. Currently the native code will only do pre-dispatching of system keys, to avoid significant overhead for gaming input. This should be improved to be smarted, filtering for only keys that the IME is interested in. Unfortunately IMEs don't currently provide this information. :p Change-Id: Ic1c7aeec8b348164957f2cd88119eb5bd85c2a9f --- core/java/android/app/NativeActivity.java | 33 +++ core/jni/android_app_NativeActivity.cpp | 268 ++++++++++++++---- .../android_app_NativeActivity.h | 72 ++++- include/ui/Input.h | 2 + libs/ui/Input.cpp | 17 ++ native/android/input.cpp | 6 +- native/include/android/input.h | 12 +- 7 files changed, 347 insertions(+), 63 deletions(-) diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java index 3238b82b447c1..eaf06757b6f7b 100644 --- a/core/java/android/app/NativeActivity.java +++ b/core/java/android/app/NativeActivity.java @@ -1,5 +1,8 @@ package android.app; +import com.android.internal.view.IInputMethodCallback; +import com.android.internal.view.IInputMethodSession; + import dalvik.system.PathClassLoader; import android.content.Context; @@ -25,6 +28,7 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.inputmethod.InputMethodManager; import java.io.File; +import java.lang.ref.WeakReference; /** * Convenience for implementing an activity that will be implemented @@ -36,6 +40,7 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, private NativeContentView mNativeContentView; private InputMethodManager mIMM; + private InputMethodCallback mInputMethodCallback; private int mNativeHandle; @@ -73,6 +78,7 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, private native void onInputChannelDestroyedNative(int handle, InputChannel channel); private native void onContentRectChangedNative(int handle, int x, int y, int w, int h); private native void dispatchKeyEventNative(int handle, KeyEvent event); + private native void finishPreDispatchKeyEventNative(int handle, int seq, boolean handled); static class NativeContentView extends View { NativeActivity mActivity; @@ -86,12 +92,34 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, } } + static class InputMethodCallback extends IInputMethodCallback.Stub { + WeakReference mNa; + + InputMethodCallback(NativeActivity na) { + mNa = new WeakReference(na); + } + + @Override + public void finishedEvent(int seq, boolean handled) { + NativeActivity na = mNa.get(); + if (na != null) { + na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled); + } + } + + @Override + public void sessionCreated(IInputMethodSession session) { + // Stub -- not for use in the client. + } + } + @Override protected void onCreate(Bundle savedInstanceState) { String libname = "main"; ActivityInfo ai; mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + mInputMethodCallback = new InputMethodCallback(this); getWindow().takeSurface(this); getWindow().takeInputQueue(this); @@ -292,6 +320,11 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, } } + void preDispatchKeyEvent(KeyEvent event, int seq) { + mIMM.dispatchKeyEvent(this, seq, event, + mInputMethodCallback); + } + void setWindowFlags(int flags, int mask) { getWindow().setFlags(flags, mask); } diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp index c17b50495d9f2..1feb3b3ecda94 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -45,6 +45,7 @@ static struct { jclass clazz; jmethodID dispatchUnhandledKeyEvent; + jmethodID preDispatchKeyEvent; jmethodID setWindowFlags; jmethodID setWindowFormat; jmethodID showIme; @@ -104,7 +105,7 @@ static bool read_work(int fd, ActivityWork* outWork) { using namespace android; AInputQueue::AInputQueue(const sp& channel, int workWrite) : - mWorkWrite(workWrite), mConsumer(channel) { + mWorkWrite(workWrite), mConsumer(channel), mSeq(0) { int msgpipe[2]; if (pipe(msgpipe)) { LOGW("could not create pipe: %s", strerror(errno)); @@ -157,6 +158,8 @@ int32_t AInputQueue::hasEvents() { int32_t AInputQueue::getEvent(AInputEvent** outEvent) { *outEvent = NULL; + bool finishNow = false; + char byteread; ssize_t nRead = read(mDispatchKeyRead, &byteread, 1); if (nRead == 1) { @@ -165,10 +168,34 @@ int32_t AInputQueue::getEvent(AInputEvent** outEvent) { KeyEvent* kevent = mDispatchingKeys[0]; *outEvent = kevent; mDispatchingKeys.removeAt(0); - mDeliveringKeys.add(kevent); + in_flight_event inflight; + inflight.event = kevent; + inflight.seq = -1; + inflight.doFinish = false; + mInFlightEvents.push(inflight); + } + if (mFinishPreDispatches.size() > 0) { + finish_pre_dispatch finish(mFinishPreDispatches[0]); + mFinishPreDispatches.removeAt(0); + const size_t N = mInFlightEvents.size(); + for (size_t i=0; igetName().string(), res); @@ -189,39 +216,69 @@ int32_t AInputQueue::getEvent(AInputEvent** outEvent) { return -1; } + in_flight_event inflight; + inflight.event = myEvent; + inflight.seq = -1; + inflight.doFinish = true; + mInFlightEvents.push(inflight); + *outEvent = myEvent; return 0; } +bool AInputQueue::preDispatchEvent(AInputEvent* event) { + if (((InputEvent*)event)->getType() != AINPUT_EVENT_TYPE_KEY) { + // The IME only cares about key events. + return false; + } + + // For now we only send system keys to the IME... this avoids having + // critical keys like DPAD go through this path. We really need to have + // the IME report which keys it wants. + if (!((KeyEvent*)event)->isSystemKey()) { + return false; + } + + return preDispatchKey((KeyEvent*)event); +} + void AInputQueue::finishEvent(AInputEvent* event, bool handled) { - bool needFinished = true; + LOG_TRACE("finishEvent: %p handled=%d", event, handled ? 1 : 0); if (!handled && ((InputEvent*)event)->getType() == AINPUT_EVENT_TYPE_KEY && ((KeyEvent*)event)->hasDefaultAction()) { // The app didn't handle this, but it may have a default action // associated with it. We need to hand this back to Java to be // executed. - doDefaultKey((KeyEvent*)event); - needFinished = false; + doUnhandledKey((KeyEvent*)event); + return; } - const size_t N = mDeliveringKeys.size(); + mLock.lock(); + const size_t N = mInFlightEvents.size(); for (size_t i=0; igetName().string(), res); + } + } + if (static_cast(event)->getType() == AINPUT_EVENT_TYPE_KEY) { + mAvailKeyEvents.push(static_cast(event)); + } else { + mAvailMotionEvents.push(static_cast(event)); + } + mInFlightEvents.removeAt(i); + mLock.unlock(); + return; } } + mLock.unlock(); - if (needFinished) { - int32_t res = mConsumer.sendFinishedSignal(); - if (res != android::OK) { - LOGW("Failed to send finished signal on channel '%s'. status=%d", - mConsumer.getChannel()->getName().string(), res); - } - } + LOGW("finishEvent called for unknown event: %p", event); } void AInputQueue::dispatchEvent(android::KeyEvent* event) { @@ -229,8 +286,120 @@ void AInputQueue::dispatchEvent(android::KeyEvent* event) { LOG_TRACE("dispatchEvent: dispatching=%d write=%d\n", mDispatchingKeys.size(), mDispatchKeyWrite); mDispatchingKeys.add(event); + wakeupDispatch(); mLock.unlock(); - +} + +void AInputQueue::finishPreDispatch(int seq, bool handled) { + mLock.lock(); + LOG_TRACE("finishPreDispatch: seq=%d handled=%d\n", seq, handled ? 1 : 0); + finish_pre_dispatch finish; + finish.seq = seq; + finish.handled = handled; + mFinishPreDispatches.add(finish); + wakeupDispatch(); + mLock.unlock(); +} + +KeyEvent* AInputQueue::consumeUnhandledEvent() { + KeyEvent* event = NULL; + + mLock.lock(); + if (mUnhandledKeys.size() > 0) { + event = mUnhandledKeys[0]; + mUnhandledKeys.removeAt(0); + } + mLock.unlock(); + + LOG_TRACE("consumeUnhandledEvent: KeyEvent=%p", event); + + return event; +} + +KeyEvent* AInputQueue::consumePreDispatchingEvent(int* outSeq) { + KeyEvent* event = NULL; + + mLock.lock(); + if (mPreDispatchingKeys.size() > 0) { + const in_flight_event& inflight(mPreDispatchingKeys[0]); + event = static_cast(inflight.event); + *outSeq = inflight.seq; + mPreDispatchingKeys.removeAt(0); + } + mLock.unlock(); + + LOG_TRACE("consumePreDispatchingEvent: KeyEvent=%p", event); + + return event; +} + +KeyEvent* AInputQueue::createKeyEvent() { + mLock.lock(); + KeyEvent* event; + if (mAvailKeyEvents.size() <= 0) { + event = new KeyEvent(); + } else { + event = mAvailKeyEvents.top(); + mAvailKeyEvents.pop(); + } + mLock.unlock(); + return event; +} + +MotionEvent* AInputQueue::createMotionEvent() { + mLock.lock(); + MotionEvent* event; + if (mAvailMotionEvents.size() <= 0) { + event = new MotionEvent(); + } else { + event = mAvailMotionEvents.top(); + mAvailMotionEvents.pop(); + } + mLock.unlock(); + return event; +} + +void AInputQueue::doUnhandledKey(KeyEvent* keyEvent) { + mLock.lock(); + LOG_TRACE("Unhandled key: pending=%d write=%d\n", mUnhandledKeys.size(), mWorkWrite); + if (mUnhandledKeys.size() <= 0 && mWorkWrite >= 0) { + write_work(mWorkWrite, CMD_DEF_KEY); + } + mUnhandledKeys.add(keyEvent); + mLock.unlock(); +} + +bool AInputQueue::preDispatchKey(KeyEvent* keyEvent) { + mLock.lock(); + LOG_TRACE("preDispatch key: pending=%d write=%d\n", mPreDispatchingKeys.size(), mWorkWrite); + const size_t N = mInFlightEvents.size(); + for (size_t i=0; i= 0) { + // This event has already been pre-dispatched! + LOG_TRACE("Event already pre-dispatched!"); + mLock.unlock(); + return false; + } + mSeq++; + if (mSeq < 0) mSeq = 1; + inflight.seq = mSeq; + + if (mPreDispatchingKeys.size() <= 0 && mWorkWrite >= 0) { + write_work(mWorkWrite, CMD_DEF_KEY); + } + mPreDispatchingKeys.add(inflight); + mLock.unlock(); + return true; + } + } + + LOGW("preDispatchKey called for unknown event: %p", keyEvent); + return false; +} + +void AInputQueue::wakeupDispatch() { restart: char dummy = 0; int res = write(mDispatchKeyWrite, &dummy, sizeof(dummy)); @@ -244,31 +413,6 @@ restart: else LOGW("Truncated writing to dispatch fd: %d", res); } -KeyEvent* AInputQueue::consumeUnhandledEvent() { - KeyEvent* event = NULL; - - mLock.lock(); - if (mPendingKeys.size() > 0) { - event = mPendingKeys[0]; - mPendingKeys.removeAt(0); - } - mLock.unlock(); - - LOG_TRACE("consumeUnhandledEvent: KeyEvent=%p", event); - - return event; -} - -void AInputQueue::doDefaultKey(KeyEvent* keyEvent) { - mLock.lock(); - LOG_TRACE("Default key: pending=%d write=%d\n", mPendingKeys.size(), mWorkWrite); - if (mPendingKeys.size() <= 0 && mWorkWrite >= 0) { - write_work(mWorkWrite, CMD_DEF_KEY); - } - mPendingKeys.add(keyEvent); - mLock.unlock(); -} - namespace android { // ------------------------------------------------------------------------ @@ -417,11 +561,14 @@ static bool mainWorkCallback(int fd, int events, void* data) { code->env, keyEvent); code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.dispatchUnhandledKeyEvent, inputEventObj); - int32_t res = code->nativeInputQueue->getConsumer().sendFinishedSignal(); - if (res != OK) { - LOGW("Failed to send finished signal on channel '%s'. status=%d", - code->nativeInputQueue->getConsumer().getChannel()->getName().string(), res); - } + code->nativeInputQueue->finishEvent(keyEvent, true); + } + int seq; + while ((keyEvent=code->nativeInputQueue->consumePreDispatchingEvent(&seq)) != NULL) { + jobject inputEventObj = android_view_KeyEvent_fromNative( + code->env, keyEvent); + code->env->CallVoidMethod(code->clazz, + gNativeActivityClassInfo.preDispatchKeyEvent, inputEventObj, seq); } } break; case CMD_SET_WINDOW_FORMAT: { @@ -766,13 +913,26 @@ dispatchKeyEvent_native(JNIEnv* env, jobject clazz, jint handle, jobject eventOb if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->nativeInputQueue != NULL) { - KeyEvent* event = new KeyEvent(); + KeyEvent* event = code->nativeInputQueue->createKeyEvent(); android_view_KeyEvent_toNative(env, eventObj, event); code->nativeInputQueue->dispatchEvent(event); } } } +static void +finishPreDispatchKeyEvent_native(JNIEnv* env, jobject clazz, jint handle, + jint seq, jboolean handled) +{ + LOG_TRACE("finishPreDispatchKeyEvent_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->nativeInputQueue != NULL) { + code->nativeInputQueue->finishPreDispatch(seq, handled ? true : false); + } + } +} + static const JNINativeMethod g_methods[] = { { "loadNativeCode", "(Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;)I", (void*)loadNativeCode_native }, @@ -792,6 +952,7 @@ static const JNINativeMethod g_methods[] = { { "onInputChannelDestroyedNative", "(ILandroid/view/InputChannel;)V", (void*)onInputChannelDestroyed_native }, { "onContentRectChangedNative", "(IIIII)V", (void*)onContentRectChanged_native }, { "dispatchKeyEventNative", "(ILandroid/view/KeyEvent;)V", (void*)dispatchKeyEvent_native }, + { "finishPreDispatchKeyEventNative", "(IIZ)V", (void*)finishPreDispatchKeyEvent_native }, }; static const char* const kNativeActivityPathName = "android/app/NativeActivity"; @@ -814,6 +975,9 @@ int register_android_app_NativeActivity(JNIEnv* env) GET_METHOD_ID(gNativeActivityClassInfo.dispatchUnhandledKeyEvent, gNativeActivityClassInfo.clazz, "dispatchUnhandledKeyEvent", "(Landroid/view/KeyEvent;)V"); + GET_METHOD_ID(gNativeActivityClassInfo.preDispatchKeyEvent, + gNativeActivityClassInfo.clazz, + "preDispatchKeyEvent", "(Landroid/view/KeyEvent;I)V"); GET_METHOD_ID(gNativeActivityClassInfo.setWindowFlags, gNativeActivityClassInfo.clazz, diff --git a/include/android_runtime/android_app_NativeActivity.h b/include/android_runtime/android_app_NativeActivity.h index d7a9a2c3be067..c388ba80a3bc0 100644 --- a/include/android_runtime/android_app_NativeActivity.h +++ b/include/android_runtime/android_app_NativeActivity.h @@ -42,8 +42,26 @@ extern void android_NativeActivity_hideSoftInput( /* * NDK input queue API. + * + * Here is the event flow: + * 1. Event arrives in input consumer, and is returned by getEvent(). + * 2. Application calls preDispatchEvent(): + * a. Event is assigned a sequence ID and enqueued in mPreDispatchingKeys. + * b. Main thread picks up event, hands to input method. + * c. Input method eventually returns sequence # and whether it was handled. + * d. finishPreDispatch() is called to enqueue the information. + * e. next getEvent() call will: + * - finish any pre-dispatch events that the input method handled + * - return the next pre-dispatched event that the input method didn't handle. + * f. (A preDispatchEvent() call on this event will now return false). + * 3. Application calls finishEvent() with whether it was handled. + * - If handled is true, the event is finished. + * - If handled is false, the event is put on mUnhandledKeys, and: + * a. Main thread receives event from consumeUnhandledEvent(). + * b. Java sends event through default key handler. + * c. event is finished. */ -struct AInputQueue { +struct AInputQueue : public android::InputEventFactoryInterface { public: /* Creates a consumer associated with an input channel. */ explicit AInputQueue(const android::sp& channel, int workWrite); @@ -59,8 +77,9 @@ public: int32_t getEvent(AInputEvent** outEvent); - void finishEvent(AInputEvent* event, bool handled); + bool preDispatchEvent(AInputEvent* event); + void finishEvent(AInputEvent* event, bool handled); // ---------------------------------------------------------- @@ -68,28 +87,63 @@ public: void dispatchEvent(android::KeyEvent* event); + void finishPreDispatch(int seq, bool handled); + android::KeyEvent* consumeUnhandledEvent(); + android::KeyEvent* consumePreDispatchingEvent(int* outSeq); + + virtual android::KeyEvent* createKeyEvent(); + virtual android::MotionEvent* createMotionEvent(); int mWorkWrite; private: - void doDefaultKey(android::KeyEvent* keyEvent); + void doUnhandledKey(android::KeyEvent* keyEvent); + bool preDispatchKey(android::KeyEvent* keyEvent); + void wakeupDispatch(); android::InputConsumer mConsumer; - android::PreallocatedInputEventFactory mInputEventFactory; android::sp mPollLoop; int mDispatchKeyRead; int mDispatchKeyWrite; - // This is only touched by the event reader thread. It is the current - // key events that came out of the mDispatchingKeys list and are now - //Êdelivered to the app. - android::Vector mDeliveringKeys; + struct in_flight_event { + android::InputEvent* event; + int seq; + bool doFinish; + }; + + struct finish_pre_dispatch { + int seq; + bool handled; + }; android::Mutex mLock; - android::Vector mPendingKeys; + + int mSeq; + + // Cache of previously allocated key events. + android::Vector mAvailKeyEvents; + // Cache of previously allocated motion events. + android::Vector mAvailMotionEvents; + + // All input events that are actively being processed. + android::Vector mInFlightEvents; + + // Key events that the app didn't handle, and are pending for + // delivery to the activity's default key handling. + android::Vector mUnhandledKeys; + + // Keys that arrived in the Java framework and need to be + // dispatched to the app. android::Vector mDispatchingKeys; + + // Key events that are pending to be pre-dispatched to the IME. + android::Vector mPreDispatchingKeys; + + // Event sequence numbers that we have finished pre-dispatching. + android::Vector mFinishPreDispatches; }; #endif // _ANDROID_APP_NATIVEACTIVITY_H diff --git a/include/ui/Input.h b/include/ui/Input.h index f06988832d71b..d9b109197c8b5 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -152,6 +152,7 @@ public: protected: void initialize(int32_t deviceId, int32_t source); + void initialize(const InputEvent& from); private: int32_t mDeviceId; @@ -202,6 +203,7 @@ public: int32_t repeatCount, nsecs_t downTime, nsecs_t eventTime); + void initialize(const KeyEvent& from); private: int32_t mAction; diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp index e5f014f421d6b..5253c72aa3828 100644 --- a/libs/ui/Input.cpp +++ b/libs/ui/Input.cpp @@ -18,6 +18,11 @@ void InputEvent::initialize(int32_t deviceId, int32_t source) { mSource = source; } +void InputEvent::initialize(const InputEvent& from) { + mDeviceId = from.mDeviceId; + mSource = from.mSource; +} + // class KeyEvent bool KeyEvent::hasDefaultAction(int32_t keyCode) { @@ -106,6 +111,18 @@ void KeyEvent::initialize( mEventTime = eventTime; } +void KeyEvent::initialize(const KeyEvent& from) { + InputEvent::initialize(from); + mAction = from.mAction; + mFlags = from.mFlags; + mKeyCode = from.mKeyCode; + mScanCode = from.mScanCode; + mMetaState = from.mMetaState; + mRepeatCount = from.mRepeatCount; + mDownTime = from.mDownTime; + mEventTime = from.mEventTime; +} + // class MotionEvent void MotionEvent::initialize( diff --git a/native/android/input.cpp b/native/android/input.cpp index 4e1b6dcb042db..59bf71178f7cb 100644 --- a/native/android/input.cpp +++ b/native/android/input.cpp @@ -248,7 +248,7 @@ void AInputQueue_detachLooper(AInputQueue* queue) { queue->detachLooper(); } -int AInputQueue_hasEvents(AInputQueue* queue) { +int32_t AInputQueue_hasEvents(AInputQueue* queue) { return queue->hasEvents(); } @@ -256,6 +256,10 @@ int32_t AInputQueue_getEvent(AInputQueue* queue, AInputEvent** outEvent) { return queue->getEvent(outEvent); } +int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event) { + return queue->preDispatchEvent(event) ? 1 : 0; +} + void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled) { queue->finishEvent(event, handled != 0); } diff --git a/native/include/android/input.h b/native/include/android/input.h index ce79cd489c35a..0b8c7e49d246b 100644 --- a/native/include/android/input.h +++ b/native/include/android/input.h @@ -607,7 +607,7 @@ void AInputQueue_detachLooper(AInputQueue* queue); * input queue. Returns 1 if the queue has events; 0 if * it does not have events; and a negative value if there is an error. */ -int AInputQueue_hasEvents(AInputQueue* queue); +int32_t AInputQueue_hasEvents(AInputQueue* queue); /* * Returns the next available event from the queue. Returns a negative @@ -615,6 +615,16 @@ int AInputQueue_hasEvents(AInputQueue* queue); */ int32_t AInputQueue_getEvent(AInputQueue* queue, AInputEvent** outEvent); +/* + * Sends the key for standard pre-dispatching -- that is, possibly deliver + * it to the current IME to be consumed before the app. Returns 0 if it + * was not pre-dispatched, meaning you can process it right now. If non-zero + * is returned, you must abandon the current event processing and allow the + * event to appear again in the event queue (if it does not get consumed during + * pre-dispatching). + */ +int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event); + /* * Report that dispatching has finished with the given event. * This must be called after receiving an event with AInputQueue_get_event().