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
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 3238b82..eaf0675 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.inputmethod.InputMethodManager;
 
 import java.io.File;
+import java.lang.ref.WeakReference;
 
 /**
  * Convenience for implementing an activity that will be implemented
@@ -36,6 +40,7 @@
     
     private NativeContentView mNativeContentView;
     private InputMethodManager mIMM;
+    private InputMethodCallback mInputMethodCallback;
 
     private int mNativeHandle;
     
@@ -73,6 +78,7 @@
     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 @@
         }
     }
     
+    static class InputMethodCallback extends IInputMethodCallback.Stub {
+        WeakReference<NativeActivity> mNa;
+
+        InputMethodCallback(NativeActivity na) {
+            mNa = new WeakReference<NativeActivity>(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 @@
         }
     }
     
+    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 c17b504..1feb3b3 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -45,6 +45,7 @@
     jclass clazz;
 
     jmethodID dispatchUnhandledKeyEvent;
+    jmethodID preDispatchKeyEvent;
     jmethodID setWindowFlags;
     jmethodID setWindowFormat;
     jmethodID showIme;
@@ -104,7 +105,7 @@
 using namespace android;
 
 AInputQueue::AInputQueue(const sp<InputChannel>& 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::getEvent(AInputEvent** outEvent) {
     *outEvent = NULL;
 
+    bool finishNow = false;
+
     char byteread;
     ssize_t nRead = read(mDispatchKeyRead, &byteread, 1);
     if (nRead == 1) {
@@ -165,10 +168,34 @@
             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; i<N; i++) {
+                const in_flight_event& inflight(mInFlightEvents[i]);
+                if (inflight.seq == finish.seq) {
+                    *outEvent = inflight.event;
+                    finishNow = finish.handled;
+                }
+            }
+            if (*outEvent == NULL) {
+                LOGW("getEvent couldn't find inflight for seq %d", finish.seq);
+            }
         }
         mLock.unlock();
-        if (*outEvent != NULL) {
+
+        if (finishNow) {
+            finishEvent(*outEvent, true);
+            *outEvent = NULL;
+            return -1;
+        } else if (*outEvent != NULL) {
             return 0;
         }
     }
@@ -181,7 +208,7 @@
     }
 
     InputEvent* myEvent = NULL;
-    res = mConsumer.consume(&mInputEventFactory, &myEvent);
+    res = mConsumer.consume(this, &myEvent);
     if (res != android::OK) {
         LOGW("channel '%s' ~ Failed to consume input event.  status=%d",
                 mConsumer.getChannel()->getName().string(), res);
@@ -189,39 +216,69 @@
         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; i<N; i++) {
-        if (mDeliveringKeys[i] == event) {
-            delete event;
-            mDeliveringKeys.removeAt(i);
-            needFinished = false;
-            break;
+        const in_flight_event& inflight(mInFlightEvents[i]);
+        if (inflight.event == event) {
+            if (inflight.doFinish) {
+                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);
+                }
+            }
+            if (static_cast<InputEvent*>(event)->getType() == AINPUT_EVENT_TYPE_KEY) {
+                mAvailKeyEvents.push(static_cast<KeyEvent*>(event));
+            } else {
+                mAvailMotionEvents.push(static_cast<MotionEvent*>(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 @@
     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<KeyEvent*>(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<N; i++) {
+        in_flight_event& inflight(mInFlightEvents.editItemAt(i));
+        if (inflight.event == keyEvent) {
+            if (inflight.seq >= 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 @@
     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 @@
                         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 @@
     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 @@
     { "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 @@
     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 d7a9a2c..c388ba8 100644
--- a/include/android_runtime/android_app_NativeActivity.h
+++ b/include/android_runtime/android_app_NativeActivity.h
@@ -42,8 +42,26 @@
 
 /*
  * 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<android::InputChannel>& channel, int workWrite);
@@ -59,8 +77,9 @@
 
     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 @@
 
     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<android::PollLoop> 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<android::KeyEvent*> 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<android::KeyEvent*> mPendingKeys;
+
+    int mSeq;
+
+    // Cache of previously allocated key events.
+    android::Vector<android::KeyEvent*> mAvailKeyEvents;
+    // Cache of previously allocated motion events.
+    android::Vector<android::MotionEvent*> mAvailMotionEvents;
+
+    // All input events that are actively being processed.
+    android::Vector<in_flight_event> mInFlightEvents;
+
+    // Key events that the app didn't handle, and are pending for
+    // delivery to the activity's default key handling.
+    android::Vector<android::KeyEvent*> mUnhandledKeys;
+
+    // Keys that arrived in the Java framework and need to be
+    // dispatched to the app.
     android::Vector<android::KeyEvent*> mDispatchingKeys;
+
+    // Key events that are pending to be pre-dispatched to the IME.
+    android::Vector<in_flight_event> mPreDispatchingKeys;
+
+    // Event sequence numbers that we have finished pre-dispatching.
+    android::Vector<finish_pre_dispatch> mFinishPreDispatches;
 };
 
 #endif // _ANDROID_APP_NATIVEACTIVITY_H
diff --git a/include/ui/Input.h b/include/ui/Input.h
index f069888..d9b1091 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -152,6 +152,7 @@
     
 protected:
     void initialize(int32_t deviceId, int32_t source);
+    void initialize(const InputEvent& from);
 
 private:
     int32_t mDeviceId;
@@ -202,6 +203,7 @@
             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 e5f014f..5253c72 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -18,6 +18,11 @@
     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 @@
     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 4e1b6dcb..59bf711 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -248,7 +248,7 @@
     queue->detachLooper();
 }
 
-int AInputQueue_hasEvents(AInputQueue* queue) {
+int32_t AInputQueue_hasEvents(AInputQueue* queue) {
     return queue->hasEvents();
 }
 
@@ -256,6 +256,10 @@
     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 ce79cd4..0b8c7e4 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -607,7 +607,7 @@
  * 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
@@ -616,6 +616,16 @@
 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().
  */