Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c6081ce authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

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
parent c5ed5910
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
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<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 @@ 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);
    }
+208 −44
Original line number Diff line number Diff line
@@ -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<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::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; 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 @@ int32_t AInputQueue::getEvent(AInputEvent** outEvent) {
    }

    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 @@ 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; i<N; i++) {
        if (mDeliveringKeys[i] == event) {
            delete event;
            mDeliveringKeys.removeAt(i);
            needFinished = false;
            break;
        }
    }
    
    if (needFinished) {
        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();
    
    LOGW("finishEvent called for unknown event: %p", event);
}

void AInputQueue::dispatchEvent(android::KeyEvent* event) {
@@ -229,28 +286,28 @@ void AInputQueue::dispatchEvent(android::KeyEvent* event) {
    LOG_TRACE("dispatchEvent: dispatching=%d write=%d\n", mDispatchingKeys.size(),
            mDispatchKeyWrite);
    mDispatchingKeys.add(event);
    wakeupDispatch();
    mLock.unlock();
    
restart:
    char dummy = 0;
    int res = write(mDispatchKeyWrite, &dummy, sizeof(dummy));
    if (res < 0 && errno == EINTR) {
        goto restart;
}

    if (res == sizeof(dummy)) return;

    if (res < 0) LOGW("Failed writing to dispatch fd: %s", strerror(errno));
    else LOGW("Truncated writing to dispatch fd: %d", res);
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 (mPendingKeys.size() > 0) {
        event = mPendingKeys[0];
        mPendingKeys.removeAt(0);
    if (mUnhandledKeys.size() > 0) {
        event = mUnhandledKeys[0];
        mUnhandledKeys.removeAt(0);
    }
    mLock.unlock();

@@ -259,14 +316,101 @@ KeyEvent* AInputQueue::consumeUnhandledEvent() {
    return event;
}

void AInputQueue::doDefaultKey(KeyEvent* keyEvent) {
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("Default key: pending=%d write=%d\n", mPendingKeys.size(), mWorkWrite);
    if (mPendingKeys.size() <= 0 && mWorkWrite >= 0) {
    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);
            }
    mPendingKeys.add(keyEvent);
            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));
    if (res < 0 && errno == EINTR) {
        goto restart;
    }

    if (res == sizeof(dummy)) return;

    if (res < 0) LOGW("Failed writing to dispatch fd: %s", strerror(errno));
    else LOGW("Truncated writing to dispatch fd: %d", res);
}

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,
+63 −9
Original line number Diff line number Diff line
@@ -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<android::InputChannel>& 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<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
+2 −0
Original line number Diff line number Diff line
@@ -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;
+17 −0
Original line number Diff line number Diff line
@@ -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(
Loading