am 9c37e5ad: am 75a91389: Merge "Implement native key pre-dispatching to IMEs." into gingerbread

Merge commit '9c37e5add9d7678a29b4e0d1e178fe78a13db961'

* commit '9c37e5add9d7678a29b4e0d1e178fe78a13db961':
  Implement native key pre-dispatching to IMEs.
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().
  */