Refactor input system into its own service.

Extracted the input system from the window manager service into
a new input manager service.  This will make it easier to
offer new input-related features to applications.

Cleaned up the input manager service JNI layer somewhat to get rid
of all of the unnecessary checks for whether the input manager
had been initialized.  Simplified the callback layer as well.

Change-Id: I3175d01307aed1420780d3c093d2694b41edf66e
diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp
new file mode 100644
index 0000000..22795bf
--- /dev/null
+++ b/services/jni/com_android_server_input_InputManagerService.cpp
@@ -0,0 +1,1473 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputManager-JNI"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about InputReaderPolicy
+#define DEBUG_INPUT_READER_POLICY 0
+
+// Log debug messages about InputDispatcherPolicy
+#define DEBUG_INPUT_DISPATCHER_POLICY 0
+
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include <limits.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <utils/Log.h>
+#include <utils/Looper.h>
+#include <utils/threads.h>
+
+#include <input/InputManager.h>
+#include <input/PointerController.h>
+#include <input/SpriteController.h>
+
+#include <android_os_MessageQueue.h>
+#include <android_view_KeyEvent.h>
+#include <android_view_MotionEvent.h>
+#include <android_view_InputChannel.h>
+#include <android_view_PointerIcon.h>
+#include <android/graphics/GraphicsJNI.h>
+
+#include "com_android_server_PowerManagerService.h"
+#include "com_android_server_input_InputApplicationHandle.h"
+#include "com_android_server_input_InputWindowHandle.h"
+
+namespace android {
+
+// The exponent used to calculate the pointer speed scaling factor.
+// The scaling factor is calculated as 2 ^ (speed * exponent),
+// where the speed ranges from -7 to + 7 and is supplied by the user.
+static const float POINTER_SPEED_EXPONENT = 1.0f / 4;
+
+static struct {
+    jmethodID notifyConfigurationChanged;
+    jmethodID notifyLidSwitchChanged;
+    jmethodID notifyInputChannelBroken;
+    jmethodID notifyANR;
+    jmethodID filterInputEvent;
+    jmethodID interceptKeyBeforeQueueing;
+    jmethodID interceptMotionBeforeQueueingWhenScreenOff;
+    jmethodID interceptKeyBeforeDispatching;
+    jmethodID dispatchUnhandledKey;
+    jmethodID checkInjectEventsPermission;
+    jmethodID getVirtualKeyQuietTimeMillis;
+    jmethodID getExcludedDeviceNames;
+    jmethodID getKeyRepeatTimeout;
+    jmethodID getKeyRepeatDelay;
+    jmethodID getHoverTapTimeout;
+    jmethodID getHoverTapSlop;
+    jmethodID getDoubleTapTimeout;
+    jmethodID getLongPressTimeout;
+    jmethodID getPointerLayer;
+    jmethodID getPointerIcon;
+} gServiceClassInfo;
+
+static struct {
+    jclass clazz;
+} gKeyEventClassInfo;
+
+static struct {
+    jclass clazz;
+} gMotionEventClassInfo;
+
+static struct {
+    jclass clazz;
+
+    jmethodID ctor;
+    jmethodID addMotionRange;
+
+    jfieldID mId;
+    jfieldID mName;
+    jfieldID mSources;
+    jfieldID mKeyboardType;
+    jfieldID mKeyCharacterMapFile;
+} gInputDeviceClassInfo;
+
+static struct {
+    jfieldID touchscreen;
+    jfieldID keyboard;
+    jfieldID navigation;
+} gConfigurationClassInfo;
+
+
+// --- Global functions ---
+
+template<typename T>
+inline static T min(const T& a, const T& b) {
+    return a < b ? a : b;
+}
+
+template<typename T>
+inline static T max(const T& a, const T& b) {
+    return a > b ? a : b;
+}
+
+static jobject getInputApplicationHandleObjLocalRef(JNIEnv* env,
+        const sp<InputApplicationHandle>& inputApplicationHandle) {
+    if (inputApplicationHandle == NULL) {
+        return NULL;
+    }
+    return static_cast<NativeInputApplicationHandle*>(inputApplicationHandle.get())->
+            getInputApplicationHandleObjLocalRef(env);
+}
+
+static jobject getInputWindowHandleObjLocalRef(JNIEnv* env,
+        const sp<InputWindowHandle>& inputWindowHandle) {
+    if (inputWindowHandle == NULL) {
+        return NULL;
+    }
+    return static_cast<NativeInputWindowHandle*>(inputWindowHandle.get())->
+            getInputWindowHandleObjLocalRef(env);
+}
+
+static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t style,
+        SpriteIcon* outSpriteIcon) {
+    PointerIcon pointerIcon;
+    status_t status = android_view_PointerIcon_loadSystemIcon(env,
+            contextObj, style, &pointerIcon);
+    if (!status) {
+        pointerIcon.bitmap.copyTo(&outSpriteIcon->bitmap, SkBitmap::kARGB_8888_Config);
+        outSpriteIcon->hotSpotX = pointerIcon.hotSpotX;
+        outSpriteIcon->hotSpotY = pointerIcon.hotSpotY;
+    }
+}
+
+enum {
+    WM_ACTION_PASS_TO_USER = 1,
+    WM_ACTION_POKE_USER_ACTIVITY = 2,
+    WM_ACTION_GO_TO_SLEEP = 4,
+};
+
+
+// --- NativeInputManager ---
+
+class NativeInputManager : public virtual RefBase,
+    public virtual InputReaderPolicyInterface,
+    public virtual InputDispatcherPolicyInterface,
+    public virtual PointerControllerPolicyInterface {
+protected:
+    virtual ~NativeInputManager();
+
+public:
+    NativeInputManager(jobject contextObj, jobject serviceObj, const sp<Looper>& looper);
+
+    inline sp<InputManager> getInputManager() const { return mInputManager; }
+
+    void dump(String8& dump);
+
+    void setDisplaySize(int32_t displayId, int32_t width, int32_t height,
+            int32_t externalWidth, int32_t externalHeight);
+    void setDisplayOrientation(int32_t displayId, int32_t orientation);
+
+    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
+            const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+    status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
+
+    void setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray);
+    void setFocusedApplication(JNIEnv* env, jobject applicationHandleObj);
+    void setInputDispatchMode(bool enabled, bool frozen);
+    void setSystemUiVisibility(int32_t visibility);
+    void setPointerSpeed(int32_t speed);
+    void setShowTouches(bool enabled);
+
+    /* --- InputReaderPolicyInterface implementation --- */
+
+    virtual void getReaderConfiguration(InputReaderConfiguration* outConfig);
+    virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
+
+    /* --- InputDispatcherPolicyInterface implementation --- */
+
+    virtual void notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
+            uint32_t policyFlags);
+    virtual void notifyConfigurationChanged(nsecs_t when);
+    virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+            const sp<InputWindowHandle>& inputWindowHandle);
+    virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle);
+    virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags);
+    virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig);
+    virtual bool isKeyRepeatEnabled();
+    virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags);
+    virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags);
+    virtual nsecs_t interceptKeyBeforeDispatching(
+            const sp<InputWindowHandle>& inputWindowHandle,
+            const KeyEvent* keyEvent, uint32_t policyFlags);
+    virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+            const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent);
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
+    virtual bool checkInjectEventsPermissionNonReentrant(
+            int32_t injectorPid, int32_t injectorUid);
+
+    /* --- PointerControllerPolicyInterface implementation --- */
+
+    virtual void loadPointerResources(PointerResources* outResources);
+
+private:
+    sp<InputManager> mInputManager;
+
+    jobject mContextObj;
+    jobject mServiceObj;
+    sp<Looper> mLooper;
+
+    Mutex mLock;
+    struct Locked {
+        // Display size information.
+        int32_t displayWidth, displayHeight; // -1 when not initialized
+        int32_t displayExternalWidth, displayExternalHeight; // -1 when not initialized
+        int32_t displayOrientation;
+
+        // System UI visibility.
+        int32_t systemUiVisibility;
+
+        // Pointer speed.
+        int32_t pointerSpeed;
+
+        // True if pointer gestures are enabled.
+        bool pointerGesturesEnabled;
+
+        // Show touches feature enable/disable.
+        bool showTouches;
+
+        // Sprite controller singleton, created on first use.
+        sp<SpriteController> spriteController;
+
+        // Pointer controller singleton, created and destroyed as needed.
+        wp<PointerController> pointerController;
+    } mLocked;
+
+    void updateInactivityTimeoutLocked(const sp<PointerController>& controller);
+    void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
+    void ensureSpriteControllerLocked();
+
+    // Power manager interactions.
+    bool isScreenOn();
+    bool isScreenBright();
+
+    static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
+
+    static inline JNIEnv* jniEnv() {
+        return AndroidRuntime::getJNIEnv();
+    }
+};
+
+
+
+NativeInputManager::NativeInputManager(jobject contextObj,
+        jobject serviceObj, const sp<Looper>& looper) :
+        mLooper(looper) {
+    JNIEnv* env = jniEnv();
+
+    mContextObj = env->NewGlobalRef(contextObj);
+    mServiceObj = env->NewGlobalRef(serviceObj);
+
+    {
+        AutoMutex _l(mLock);
+        mLocked.displayWidth = -1;
+        mLocked.displayHeight = -1;
+        mLocked.displayExternalWidth = -1;
+        mLocked.displayExternalHeight = -1;
+        mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
+
+        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
+        mLocked.pointerSpeed = 0;
+        mLocked.pointerGesturesEnabled = true;
+        mLocked.showTouches = false;
+    }
+
+    sp<EventHub> eventHub = new EventHub();
+    mInputManager = new InputManager(eventHub, this, this);
+}
+
+NativeInputManager::~NativeInputManager() {
+    JNIEnv* env = jniEnv();
+
+    env->DeleteGlobalRef(mContextObj);
+    env->DeleteGlobalRef(mServiceObj);
+}
+
+void NativeInputManager::dump(String8& dump) {
+    mInputManager->getReader()->dump(dump);
+    dump.append("\n");
+
+    mInputManager->getDispatcher()->dump(dump);
+    dump.append("\n");
+}
+
+bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+    if (env->ExceptionCheck()) {
+        ALOGE("An exception was thrown by callback '%s'.", methodName);
+        LOGE_EX(env);
+        env->ExceptionClear();
+        return true;
+    }
+    return false;
+}
+
+void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height,
+        int32_t externalWidth, int32_t externalHeight) {
+    bool changed = false;
+    if (displayId == 0) {
+        AutoMutex _l(mLock);
+
+        if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+            changed = true;
+            mLocked.displayWidth = width;
+            mLocked.displayHeight = height;
+
+            sp<PointerController> controller = mLocked.pointerController.promote();
+            if (controller != NULL) {
+                controller->setDisplaySize(width, height);
+            }
+        }
+
+        if (mLocked.displayExternalWidth != externalWidth
+                || mLocked.displayExternalHeight != externalHeight) {
+            changed = true;
+            mLocked.displayExternalWidth = externalWidth;
+            mLocked.displayExternalHeight = externalHeight;
+        }
+    }
+
+    if (changed) {
+        mInputManager->getReader()->requestRefreshConfiguration(
+                InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    }
+}
+
+void NativeInputManager::setDisplayOrientation(int32_t displayId, int32_t orientation) {
+    bool changed = false;
+    if (displayId == 0) {
+        AutoMutex _l(mLock);
+
+        if (mLocked.displayOrientation != orientation) {
+            changed = true;
+            mLocked.displayOrientation = orientation;
+
+            sp<PointerController> controller = mLocked.pointerController.promote();
+            if (controller != NULL) {
+                controller->setDisplayOrientation(orientation);
+            }
+        }
+    }
+
+    if (changed) {
+        mInputManager->getReader()->requestRefreshConfiguration(
+                InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    }
+}
+
+status_t NativeInputManager::registerInputChannel(JNIEnv* env,
+        const sp<InputChannel>& inputChannel,
+        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
+    return mInputManager->getDispatcher()->registerInputChannel(
+            inputChannel, inputWindowHandle, monitor);
+}
+
+status_t NativeInputManager::unregisterInputChannel(JNIEnv* env,
+        const sp<InputChannel>& inputChannel) {
+    return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel);
+}
+
+void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {
+    JNIEnv* env = jniEnv();
+
+    jint virtualKeyQuietTime = env->CallIntMethod(mServiceObj,
+            gServiceClassInfo.getVirtualKeyQuietTimeMillis);
+    if (!checkAndClearExceptionFromCallback(env, "getVirtualKeyQuietTimeMillis")) {
+        outConfig->virtualKeyQuietTime = milliseconds_to_nanoseconds(virtualKeyQuietTime);
+    }
+
+    outConfig->excludedDeviceNames.clear();
+    jobjectArray excludedDeviceNames = jobjectArray(env->CallObjectMethod(mServiceObj,
+            gServiceClassInfo.getExcludedDeviceNames));
+    if (!checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && excludedDeviceNames) {
+        jsize length = env->GetArrayLength(excludedDeviceNames);
+        for (jsize i = 0; i < length; i++) {
+            jstring item = jstring(env->GetObjectArrayElement(excludedDeviceNames, i));
+            const char* deviceNameChars = env->GetStringUTFChars(item, NULL);
+            outConfig->excludedDeviceNames.add(String8(deviceNameChars));
+            env->ReleaseStringUTFChars(item, deviceNameChars);
+            env->DeleteLocalRef(item);
+        }
+        env->DeleteLocalRef(excludedDeviceNames);
+    }
+
+    jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
+            gServiceClassInfo.getHoverTapTimeout);
+    if (!checkAndClearExceptionFromCallback(env, "getHoverTapTimeout")) {
+        jint doubleTapTimeout = env->CallIntMethod(mServiceObj,
+                gServiceClassInfo.getDoubleTapTimeout);
+        if (!checkAndClearExceptionFromCallback(env, "getDoubleTapTimeout")) {
+            jint longPressTimeout = env->CallIntMethod(mServiceObj,
+                    gServiceClassInfo.getLongPressTimeout);
+            if (!checkAndClearExceptionFromCallback(env, "getLongPressTimeout")) {
+                outConfig->pointerGestureTapInterval = milliseconds_to_nanoseconds(hoverTapTimeout);
+
+                // We must ensure that the tap-drag interval is significantly shorter than
+                // the long-press timeout because the tap is held down for the entire duration
+                // of the double-tap timeout.
+                jint tapDragInterval = max(min(longPressTimeout - 100,
+                        doubleTapTimeout), hoverTapTimeout);
+                outConfig->pointerGestureTapDragInterval =
+                        milliseconds_to_nanoseconds(tapDragInterval);
+            }
+        }
+    }
+
+    jint hoverTapSlop = env->CallIntMethod(mServiceObj,
+            gServiceClassInfo.getHoverTapSlop);
+    if (!checkAndClearExceptionFromCallback(env, "getHoverTapSlop")) {
+        outConfig->pointerGestureTapSlop = hoverTapSlop;
+    }
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
+                * POINTER_SPEED_EXPONENT);
+        outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
+
+        outConfig->showTouches = mLocked.showTouches;
+
+        outConfig->setDisplayInfo(0, false /*external*/,
+                mLocked.displayWidth, mLocked.displayHeight, mLocked.displayOrientation);
+        outConfig->setDisplayInfo(0, true /*external*/,
+                mLocked.displayExternalWidth, mLocked.displayExternalHeight,
+                mLocked.displayOrientation);
+    } // release lock
+}
+
+sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32_t deviceId) {
+    AutoMutex _l(mLock);
+
+    sp<PointerController> controller = mLocked.pointerController.promote();
+    if (controller == NULL) {
+        ensureSpriteControllerLocked();
+
+        controller = new PointerController(this, mLooper, mLocked.spriteController);
+        mLocked.pointerController = controller;
+
+        controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight);
+        controller->setDisplayOrientation(mLocked.displayOrientation);
+
+        JNIEnv* env = jniEnv();
+        jobject pointerIconObj = env->CallObjectMethod(mServiceObj,
+                gServiceClassInfo.getPointerIcon);
+        if (!checkAndClearExceptionFromCallback(env, "getPointerIcon")) {
+            PointerIcon pointerIcon;
+            status_t status = android_view_PointerIcon_load(env, pointerIconObj,
+                    mContextObj, &pointerIcon);
+            if (!status && !pointerIcon.isNullIcon()) {
+                controller->setPointerIcon(SpriteIcon(pointerIcon.bitmap,
+                        pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+            } else {
+                controller->setPointerIcon(SpriteIcon());
+            }
+            env->DeleteLocalRef(pointerIconObj);
+        }
+
+        updateInactivityTimeoutLocked(controller);
+    }
+    return controller;
+}
+
+void NativeInputManager::ensureSpriteControllerLocked() {
+    if (mLocked.spriteController == NULL) {
+        JNIEnv* env = jniEnv();
+        jint layer = env->CallIntMethod(mServiceObj, gServiceClassInfo.getPointerLayer);
+        if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) {
+            layer = -1;
+        }
+        mLocked.spriteController = new SpriteController(mLooper, layer);
+    }
+}
+
+void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,
+        int32_t switchValue, uint32_t policyFlags) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+    ALOGD("notifySwitch - when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x",
+            when, switchCode, switchValue, policyFlags);
+#endif
+
+    JNIEnv* env = jniEnv();
+
+    switch (switchCode) {
+    case SW_LID:
+        env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyLidSwitchChanged,
+                when, switchValue == 0);
+        checkAndClearExceptionFromCallback(env, "notifyLidSwitchChanged");
+        break;
+    }
+}
+
+void NativeInputManager::notifyConfigurationChanged(nsecs_t when) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+    ALOGD("notifyConfigurationChanged - when=%lld", when);
+#endif
+
+    JNIEnv* env = jniEnv();
+
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyConfigurationChanged, when);
+    checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged");
+}
+
+nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+        const sp<InputWindowHandle>& inputWindowHandle) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+    ALOGD("notifyANR");
+#endif
+
+    JNIEnv* env = jniEnv();
+
+    jobject inputApplicationHandleObj =
+            getInputApplicationHandleObjLocalRef(env, inputApplicationHandle);
+    jobject inputWindowHandleObj =
+            getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+
+    jlong newTimeout = env->CallLongMethod(mServiceObj,
+                gServiceClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj);
+    if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
+        newTimeout = 0; // abort dispatch
+    } else {
+        assert(newTimeout >= 0);
+    }
+
+    env->DeleteLocalRef(inputWindowHandleObj);
+    env->DeleteLocalRef(inputApplicationHandleObj);
+    return newTimeout;
+}
+
+void NativeInputManager::notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+    ALOGD("notifyInputChannelBroken");
+#endif
+
+    JNIEnv* env = jniEnv();
+
+    jobject inputWindowHandleObj =
+            getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+    if (inputWindowHandleObj) {
+        env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken,
+                inputWindowHandleObj);
+        checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");
+
+        env->DeleteLocalRef(inputWindowHandleObj);
+    }
+}
+
+void NativeInputManager::getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) {
+    JNIEnv* env = jniEnv();
+
+    jint keyRepeatTimeout = env->CallIntMethod(mServiceObj,
+            gServiceClassInfo.getKeyRepeatTimeout);
+    if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatTimeout")) {
+        outConfig->keyRepeatTimeout = milliseconds_to_nanoseconds(keyRepeatTimeout);
+    }
+
+    jint keyRepeatDelay = env->CallIntMethod(mServiceObj,
+            gServiceClassInfo.getKeyRepeatDelay);
+    if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatDelay")) {
+        outConfig->keyRepeatDelay = milliseconds_to_nanoseconds(keyRepeatDelay);
+    }
+}
+
+bool NativeInputManager::isKeyRepeatEnabled() {
+    // Only enable automatic key repeating when the screen is on.
+    return isScreenOn();
+}
+
+void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray) {
+    Vector<sp<InputWindowHandle> > windowHandles;
+
+    if (windowHandleObjArray) {
+        jsize length = env->GetArrayLength(windowHandleObjArray);
+        for (jsize i = 0; i < length; i++) {
+            jobject windowHandleObj = env->GetObjectArrayElement(windowHandleObjArray, i);
+            if (! windowHandleObj) {
+                break; // found null element indicating end of used portion of the array
+            }
+
+            sp<InputWindowHandle> windowHandle =
+                    android_server_InputWindowHandle_getHandle(env, windowHandleObj);
+            if (windowHandle != NULL) {
+                windowHandles.push(windowHandle);
+            }
+            env->DeleteLocalRef(windowHandleObj);
+        }
+    }
+
+    mInputManager->getDispatcher()->setInputWindows(windowHandles);
+
+    // Do this after the dispatcher has updated the window handle state.
+    bool newPointerGesturesEnabled = true;
+    size_t numWindows = windowHandles.size();
+    for (size_t i = 0; i < numWindows; i++) {
+        const sp<InputWindowHandle>& windowHandle = windowHandles.itemAt(i);
+        const InputWindowInfo* windowInfo = windowHandle->getInfo();
+        if (windowInfo && windowInfo->hasFocus && (windowInfo->inputFeatures
+                & InputWindowInfo::INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES)) {
+            newPointerGesturesEnabled = false;
+        }
+    }
+
+    uint32_t changes = 0;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mLocked.pointerGesturesEnabled != newPointerGesturesEnabled) {
+            mLocked.pointerGesturesEnabled = newPointerGesturesEnabled;
+            changes |= InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT;
+        }
+    } // release lock
+
+    if (changes) {
+        mInputManager->getReader()->requestRefreshConfiguration(changes);
+    }
+}
+
+void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationHandleObj) {
+    sp<InputApplicationHandle> applicationHandle =
+            android_server_InputApplicationHandle_getHandle(env, applicationHandleObj);
+    mInputManager->getDispatcher()->setFocusedApplication(applicationHandle);
+}
+
+void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) {
+    mInputManager->getDispatcher()->setInputDispatchMode(enabled, frozen);
+}
+
+void NativeInputManager::setSystemUiVisibility(int32_t visibility) {
+    AutoMutex _l(mLock);
+
+    if (mLocked.systemUiVisibility != visibility) {
+        mLocked.systemUiVisibility = visibility;
+
+        sp<PointerController> controller = mLocked.pointerController.promote();
+        if (controller != NULL) {
+            updateInactivityTimeoutLocked(controller);
+        }
+    }
+}
+
+void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) {
+    bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
+    controller->setInactivityTimeout(lightsOut
+            ? PointerController::INACTIVITY_TIMEOUT_SHORT
+            : PointerController::INACTIVITY_TIMEOUT_NORMAL);
+}
+
+void NativeInputManager::setPointerSpeed(int32_t speed) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mLocked.pointerSpeed == speed) {
+            return;
+        }
+
+        ALOGI("Setting pointer speed to %d.", speed);
+        mLocked.pointerSpeed = speed;
+    } // release lock
+
+    mInputManager->getReader()->requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_POINTER_SPEED);
+}
+
+void NativeInputManager::setShowTouches(bool enabled) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mLocked.showTouches == enabled) {
+            return;
+        }
+
+        ALOGI("Setting show touches feature to %s.", enabled ? "enabled" : "disabled");
+        mLocked.showTouches = enabled;
+    } // release lock
+
+    mInputManager->getReader()->requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
+}
+
+bool NativeInputManager::isScreenOn() {
+    return android_server_PowerManagerService_isScreenOn();
+}
+
+bool NativeInputManager::isScreenBright() {
+    return android_server_PowerManagerService_isScreenBright();
+}
+
+bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
+    jobject inputEventObj;
+
+    JNIEnv* env = jniEnv();
+    switch (inputEvent->getType()) {
+    case AINPUT_EVENT_TYPE_KEY:
+        inputEventObj = android_view_KeyEvent_fromNative(env,
+                static_cast<const KeyEvent*>(inputEvent));
+        break;
+    case AINPUT_EVENT_TYPE_MOTION:
+        inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
+                static_cast<const MotionEvent*>(inputEvent));
+        break;
+    default:
+        return true; // dispatch the event normally
+    }
+
+    if (!inputEventObj) {
+        ALOGE("Failed to obtain input event object for filterInputEvent.");
+        return true; // dispatch the event normally
+    }
+
+    // The callee is responsible for recycling the event.
+    jboolean pass = env->CallBooleanMethod(mServiceObj, gServiceClassInfo.filterInputEvent,
+            inputEventObj, policyFlags);
+    if (checkAndClearExceptionFromCallback(env, "filterInputEvent")) {
+        pass = true;
+    }
+    env->DeleteLocalRef(inputEventObj);
+    return pass;
+}
+
+void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
+        uint32_t& policyFlags) {
+    // Policy:
+    // - Ignore untrusted events and pass them along.
+    // - Ask the window manager what to do with normal events and trusted injected events.
+    // - For normal events wake and brighten the screen if currently off or dim.
+    if ((policyFlags & POLICY_FLAG_TRUSTED)) {
+        nsecs_t when = keyEvent->getEventTime();
+        bool isScreenOn = this->isScreenOn();
+        bool isScreenBright = this->isScreenBright();
+
+        JNIEnv* env = jniEnv();
+        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
+        jint wmActions;
+        if (keyEventObj) {
+            wmActions = env->CallIntMethod(mServiceObj,
+                    gServiceClassInfo.interceptKeyBeforeQueueing,
+                    keyEventObj, policyFlags, isScreenOn);
+            if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
+                wmActions = 0;
+            }
+            android_view_KeyEvent_recycle(env, keyEventObj);
+            env->DeleteLocalRef(keyEventObj);
+        } else {
+            ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
+            wmActions = 0;
+        }
+
+        if (!(policyFlags & POLICY_FLAG_INJECTED)) {
+            if (!isScreenOn) {
+                policyFlags |= POLICY_FLAG_WOKE_HERE;
+            }
+
+            if (!isScreenBright) {
+                policyFlags |= POLICY_FLAG_BRIGHT_HERE;
+            }
+        }
+
+        handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
+    } else {
+        policyFlags |= POLICY_FLAG_PASS_TO_USER;
+    }
+}
+
+void NativeInputManager::interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
+    // Policy:
+    // - Ignore untrusted events and pass them along.
+    // - No special filtering for injected events required at this time.
+    // - Filter normal events based on screen state.
+    // - For normal events brighten (but do not wake) the screen if currently dim.
+    if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) {
+        if (isScreenOn()) {
+            policyFlags |= POLICY_FLAG_PASS_TO_USER;
+
+            if (!isScreenBright()) {
+                policyFlags |= POLICY_FLAG_BRIGHT_HERE;
+            }
+        } else {
+            JNIEnv* env = jniEnv();
+            jint wmActions = env->CallIntMethod(mServiceObj,
+                        gServiceClassInfo.interceptMotionBeforeQueueingWhenScreenOff,
+                        policyFlags);
+            if (checkAndClearExceptionFromCallback(env,
+                    "interceptMotionBeforeQueueingWhenScreenOff")) {
+                wmActions = 0;
+            }
+
+            policyFlags |= POLICY_FLAG_WOKE_HERE | POLICY_FLAG_BRIGHT_HERE;
+            handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
+        }
+    } else {
+        policyFlags |= POLICY_FLAG_PASS_TO_USER;
+    }
+}
+
+void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
+        uint32_t& policyFlags) {
+    if (wmActions & WM_ACTION_GO_TO_SLEEP) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+        ALOGD("handleInterceptActions: Going to sleep.");
+#endif
+        android_server_PowerManagerService_goToSleep(when);
+    }
+
+    if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+        ALOGD("handleInterceptActions: Poking user activity.");
+#endif
+        android_server_PowerManagerService_userActivity(when, POWER_MANAGER_BUTTON_EVENT);
+    }
+
+    if (wmActions & WM_ACTION_PASS_TO_USER) {
+        policyFlags |= POLICY_FLAG_PASS_TO_USER;
+    } else {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+        ALOGD("handleInterceptActions: Not passing key to user.");
+#endif
+    }
+}
+
+nsecs_t NativeInputManager::interceptKeyBeforeDispatching(
+        const sp<InputWindowHandle>& inputWindowHandle,
+        const KeyEvent* keyEvent, uint32_t policyFlags) {
+    // Policy:
+    // - Ignore untrusted events and pass them along.
+    // - Filter normal events and trusted injected events through the window manager policy to
+    //   handle the HOME key and the like.
+    nsecs_t result = 0;
+    if (policyFlags & POLICY_FLAG_TRUSTED) {
+        JNIEnv* env = jniEnv();
+
+        // Note: inputWindowHandle may be null.
+        jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
+        if (keyEventObj) {
+            jlong delayMillis = env->CallLongMethod(mServiceObj,
+                    gServiceClassInfo.interceptKeyBeforeDispatching,
+                    inputWindowHandleObj, keyEventObj, policyFlags);
+            bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
+            android_view_KeyEvent_recycle(env, keyEventObj);
+            env->DeleteLocalRef(keyEventObj);
+            if (!error) {
+                if (delayMillis < 0) {
+                    result = -1;
+                } else if (delayMillis > 0) {
+                    result = milliseconds_to_nanoseconds(delayMillis);
+                }
+            }
+        } else {
+            ALOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
+        }
+        env->DeleteLocalRef(inputWindowHandleObj);
+    }
+    return result;
+}
+
+bool NativeInputManager::dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+        const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) {
+    // Policy:
+    // - Ignore untrusted events and do not perform default handling.
+    bool result = false;
+    if (policyFlags & POLICY_FLAG_TRUSTED) {
+        JNIEnv* env = jniEnv();
+
+        // Note: inputWindowHandle may be null.
+        jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
+        if (keyEventObj) {
+            jobject fallbackKeyEventObj = env->CallObjectMethod(mServiceObj,
+                    gServiceClassInfo.dispatchUnhandledKey,
+                    inputWindowHandleObj, keyEventObj, policyFlags);
+            if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey")) {
+                fallbackKeyEventObj = NULL;
+            }
+            android_view_KeyEvent_recycle(env, keyEventObj);
+            env->DeleteLocalRef(keyEventObj);
+
+            if (fallbackKeyEventObj) {
+                // Note: outFallbackKeyEvent may be the same object as keyEvent.
+                if (!android_view_KeyEvent_toNative(env, fallbackKeyEventObj,
+                        outFallbackKeyEvent)) {
+                    result = true;
+                }
+                android_view_KeyEvent_recycle(env, fallbackKeyEventObj);
+                env->DeleteLocalRef(fallbackKeyEventObj);
+            }
+        } else {
+            ALOGE("Failed to obtain key event object for dispatchUnhandledKey.");
+        }
+        env->DeleteLocalRef(inputWindowHandleObj);
+    }
+    return result;
+}
+
+void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
+    android_server_PowerManagerService_userActivity(eventTime, eventType);
+}
+
+
+bool NativeInputManager::checkInjectEventsPermissionNonReentrant(
+        int32_t injectorPid, int32_t injectorUid) {
+    JNIEnv* env = jniEnv();
+    jboolean result = env->CallBooleanMethod(mServiceObj,
+            gServiceClassInfo.checkInjectEventsPermission, injectorPid, injectorUid);
+    if (checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission")) {
+        result = false;
+    }
+    return result;
+}
+
+void NativeInputManager::loadPointerResources(PointerResources* outResources) {
+    JNIEnv* env = jniEnv();
+
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_HOVER,
+            &outResources->spotHover);
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_TOUCH,
+            &outResources->spotTouch);
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_ANCHOR,
+            &outResources->spotAnchor);
+}
+
+
+// ----------------------------------------------------------------------------
+
+static jint nativeInit(JNIEnv* env, jclass clazz,
+        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
+    sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
+    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj, looper);
+    im->incStrong(serviceObj);
+    return reinterpret_cast<jint>(im);
+}
+
+static void nativeStart(JNIEnv* env, jclass clazz, jint ptr) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    status_t result = im->getInputManager()->start();
+    if (result) {
+        jniThrowRuntimeException(env, "Input manager could not be started.");
+    }
+}
+
+static void nativeSetDisplaySize(JNIEnv* env, jclass clazz, jint ptr,
+        jint displayId, jint width, jint height, jint externalWidth, jint externalHeight) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    // XXX we could get this from the SurfaceFlinger directly instead of requiring it
+    // to be passed in like this, not sure which is better but leaving it like this
+    // keeps the window manager in direct control of when display transitions propagate down
+    // to the input dispatcher
+    im->setDisplaySize(displayId, width, height, externalWidth, externalHeight);
+}
+
+static void nativeSetDisplayOrientation(JNIEnv* env, jclass clazz,
+        jint ptr, jint displayId, jint orientation) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->setDisplayOrientation(displayId, orientation);
+}
+
+static jint nativeGetScanCodeState(JNIEnv* env, jclass clazz,
+        jint ptr, jint deviceId, jint sourceMask, jint scanCode) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    return im->getInputManager()->getReader()->getScanCodeState(
+            deviceId, uint32_t(sourceMask), scanCode);
+}
+
+static jint nativeGetKeyCodeState(JNIEnv* env, jclass clazz,
+        jint ptr, jint deviceId, jint sourceMask, jint keyCode) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    return im->getInputManager()->getReader()->getKeyCodeState(
+            deviceId, uint32_t(sourceMask), keyCode);
+}
+
+static jint nativeGetSwitchState(JNIEnv* env, jclass clazz,
+        jint ptr, jint deviceId, jint sourceMask, jint sw) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    return im->getInputManager()->getReader()->getSwitchState(
+            deviceId, uint32_t(sourceMask), sw);
+}
+
+static jboolean nativeHasKeys(JNIEnv* env, jclass clazz,
+        jint ptr, jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    int32_t* codes = env->GetIntArrayElements(keyCodes, NULL);
+    uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL);
+    jsize numCodes = env->GetArrayLength(keyCodes);
+    jboolean result;
+    if (numCodes == env->GetArrayLength(keyCodes)) {
+        result = im->getInputManager()->getReader()->hasKeys(
+                deviceId, uint32_t(sourceMask), numCodes, codes, flags);
+    } else {
+        result = JNI_FALSE;
+    }
+
+    env->ReleaseBooleanArrayElements(outFlags, flags, 0);
+    env->ReleaseIntArrayElements(keyCodes, codes, 0);
+    return result;
+}
+
+static void throwInputChannelNotInitialized(JNIEnv* env) {
+    jniThrowException(env, "java/lang/IllegalStateException",
+             "inputChannel is not initialized");
+}
+
+static void handleInputChannelDisposed(JNIEnv* env,
+        jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) {
+    NativeInputManager* im = static_cast<NativeInputManager*>(data);
+
+    ALOGW("Input channel object '%s' was disposed without first being unregistered with "
+            "the input manager!", inputChannel->getName().string());
+    im->unregisterInputChannel(env, inputChannel);
+}
+
+static void nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
+        jint ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == NULL) {
+        throwInputChannelNotInitialized(env);
+        return;
+    }
+
+    sp<InputWindowHandle> inputWindowHandle =
+            android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);
+
+    status_t status = im->registerInputChannel(
+            env, inputChannel, inputWindowHandle, monitor);
+    if (status) {
+        String8 message;
+        message.appendFormat("Failed to register input channel.  status=%d", status);
+        jniThrowRuntimeException(env, message.string());
+        return;
+    }
+
+    if (! monitor) {
+        android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
+                handleInputChannelDisposed, im);
+    }
+}
+
+static void nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
+        jint ptr, jobject inputChannelObj) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == NULL) {
+        throwInputChannelNotInitialized(env);
+        return;
+    }
+
+    android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL);
+
+    status_t status = im->unregisterInputChannel(env, inputChannel);
+    if (status && status != BAD_VALUE) { // ignore already unregistered channel
+        String8 message;
+        message.appendFormat("Failed to unregister input channel.  status=%d", status);
+        jniThrowRuntimeException(env, message.string());
+    }
+}
+
+static void nativeSetInputFilterEnabled(JNIEnv* env, jclass clazz,
+        jint ptr, jboolean enabled) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->getInputManager()->getDispatcher()->setInputFilterEnabled(enabled);
+}
+
+static jint nativeInjectInputEvent(JNIEnv* env, jclass clazz,
+        jint ptr, jobject inputEventObj, jint injectorPid, jint injectorUid,
+        jint syncMode, jint timeoutMillis, jint policyFlags) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
+        KeyEvent keyEvent;
+        status_t status = android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
+        if (status) {
+            jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
+            return INPUT_EVENT_INJECTION_FAILED;
+        }
+
+        return im->getInputManager()->getDispatcher()->injectInputEvent(
+                & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis,
+                uint32_t(policyFlags));
+    } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
+        const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj);
+        if (!motionEvent) {
+            jniThrowRuntimeException(env, "Could not read contents of MotionEvent object.");
+            return INPUT_EVENT_INJECTION_FAILED;
+        }
+
+        return im->getInputManager()->getDispatcher()->injectInputEvent(
+                motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis,
+                uint32_t(policyFlags));
+    } else {
+        jniThrowRuntimeException(env, "Invalid input event type.");
+        return INPUT_EVENT_INJECTION_FAILED;
+    }
+}
+
+static void nativeSetInputWindows(JNIEnv* env, jclass clazz,
+        jint ptr, jobjectArray windowHandleObjArray) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->setInputWindows(env, windowHandleObjArray);
+}
+
+static void nativeSetFocusedApplication(JNIEnv* env, jclass clazz,
+        jint ptr, jobject applicationHandleObj) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->setFocusedApplication(env, applicationHandleObj);
+}
+
+static void nativeSetInputDispatchMode(JNIEnv* env,
+        jclass clazz, jint ptr, jboolean enabled, jboolean frozen) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->setInputDispatchMode(enabled, frozen);
+}
+
+static void nativeSetSystemUiVisibility(JNIEnv* env,
+        jclass clazz, jint ptr, jint visibility) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->setSystemUiVisibility(visibility);
+}
+
+static jobject nativeGetInputDevice(JNIEnv* env,
+        jclass clazz, jint ptr, jint deviceId) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    InputDeviceInfo deviceInfo;
+    status_t status = im->getInputManager()->getReader()->getInputDeviceInfo(
+            deviceId, & deviceInfo);
+    if (status) {
+        return NULL;
+    }
+
+    jobject deviceObj = env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor);
+    if (! deviceObj) {
+        return NULL;
+    }
+
+    jstring deviceNameObj = env->NewStringUTF(deviceInfo.getName().string());
+    if (! deviceNameObj) {
+        return NULL;
+    }
+
+    jstring fileStr = env->NewStringUTF(deviceInfo.getKeyCharacterMapFile());
+    if (!fileStr) {
+        return NULL;
+    }
+
+    env->SetIntField(deviceObj, gInputDeviceClassInfo.mId, deviceInfo.getId());
+    env->SetObjectField(deviceObj, gInputDeviceClassInfo.mName, deviceNameObj);
+    env->SetIntField(deviceObj, gInputDeviceClassInfo.mSources, deviceInfo.getSources());
+    env->SetIntField(deviceObj, gInputDeviceClassInfo.mKeyboardType, deviceInfo.getKeyboardType());
+    env->SetObjectField(deviceObj, gInputDeviceClassInfo.mKeyCharacterMapFile, fileStr);
+
+    const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
+    for (size_t i = 0; i < ranges.size(); i++) {
+        const InputDeviceInfo::MotionRange& range = ranges.itemAt(i);
+        env->CallVoidMethod(deviceObj, gInputDeviceClassInfo.addMotionRange,
+                range.axis, range.source, range.min, range.max, range.flat, range.fuzz);
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+    }
+
+    return deviceObj;
+}
+
+static jintArray nativeGetInputDeviceIds(JNIEnv* env,
+        jclass clazz, jint ptr) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    Vector<int> deviceIds;
+    im->getInputManager()->getReader()->getInputDeviceIds(deviceIds);
+
+    jintArray deviceIdsObj = env->NewIntArray(deviceIds.size());
+    if (! deviceIdsObj) {
+        return NULL;
+    }
+
+    env->SetIntArrayRegion(deviceIdsObj, 0, deviceIds.size(), deviceIds.array());
+    return deviceIdsObj;
+}
+
+static void nativeGetInputConfiguration(JNIEnv* env,
+        jclass clazz, jint ptr, jobject configObj) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    InputConfiguration config;
+    im->getInputManager()->getReader()->getInputConfiguration(& config);
+
+    env->SetIntField(configObj, gConfigurationClassInfo.touchscreen, config.touchScreen);
+    env->SetIntField(configObj, gConfigurationClassInfo.keyboard, config.keyboard);
+    env->SetIntField(configObj, gConfigurationClassInfo.navigation, config.navigation);
+}
+
+static jboolean nativeTransferTouchFocus(JNIEnv* env,
+        jclass clazz, jint ptr, jobject fromChannelObj, jobject toChannelObj) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    sp<InputChannel> fromChannel =
+            android_view_InputChannel_getInputChannel(env, fromChannelObj);
+    sp<InputChannel> toChannel =
+            android_view_InputChannel_getInputChannel(env, toChannelObj);
+
+    if (fromChannel == NULL || toChannel == NULL) {
+        return false;
+    }
+
+    return im->getInputManager()->getDispatcher()->
+            transferTouchFocus(fromChannel, toChannel);
+}
+
+static void nativeSetPointerSpeed(JNIEnv* env,
+        jclass clazz, jint ptr, jint speed) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->setPointerSpeed(speed);
+}
+
+static void nativeSetShowTouches(JNIEnv* env,
+        jclass clazz, jint ptr, jboolean enabled) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->setShowTouches(enabled);
+}
+
+static jstring nativeDump(JNIEnv* env, jclass clazz, jint ptr) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    String8 dump;
+    im->dump(dump);
+    return env->NewStringUTF(dump.string());
+}
+
+static void nativeMonitor(JNIEnv* env, jclass clazz, jint ptr) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->getInputManager()->getReader()->monitor();
+    im->getInputManager()->getDispatcher()->monitor();
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gInputManagerMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeInit",
+            "(Lcom/android/server/input/InputManagerService;Landroid/content/Context;Landroid/os/MessageQueue;)I",
+            (void*) nativeInit },
+    { "nativeStart", "(I)V",
+            (void*) nativeStart },
+    { "nativeSetDisplaySize", "(IIIIII)V",
+            (void*) nativeSetDisplaySize },
+    { "nativeSetDisplayOrientation", "(III)V",
+            (void*) nativeSetDisplayOrientation },
+    { "nativeGetScanCodeState", "(IIII)I",
+            (void*) nativeGetScanCodeState },
+    { "nativeGetKeyCodeState", "(IIII)I",
+            (void*) nativeGetKeyCodeState },
+    { "nativeGetSwitchState", "(IIII)I",
+            (void*) nativeGetSwitchState },
+    { "nativeHasKeys", "(III[I[Z)Z",
+            (void*) nativeHasKeys },
+    { "nativeRegisterInputChannel",
+            "(ILandroid/view/InputChannel;Lcom/android/server/input/InputWindowHandle;Z)V",
+            (void*) nativeRegisterInputChannel },
+    { "nativeUnregisterInputChannel", "(ILandroid/view/InputChannel;)V",
+            (void*) nativeUnregisterInputChannel },
+    { "nativeSetInputFilterEnabled", "(IZ)V",
+            (void*) nativeSetInputFilterEnabled },
+    { "nativeInjectInputEvent", "(ILandroid/view/InputEvent;IIIII)I",
+            (void*) nativeInjectInputEvent },
+    { "nativeSetInputWindows", "(I[Lcom/android/server/input/InputWindowHandle;)V",
+            (void*) nativeSetInputWindows },
+    { "nativeSetFocusedApplication", "(ILcom/android/server/input/InputApplicationHandle;)V",
+            (void*) nativeSetFocusedApplication },
+    { "nativeSetInputDispatchMode", "(IZZ)V",
+            (void*) nativeSetInputDispatchMode },
+    { "nativeSetSystemUiVisibility", "(II)V",
+            (void*) nativeSetSystemUiVisibility },
+    { "nativeGetInputDevice", "(II)Landroid/view/InputDevice;",
+            (void*) nativeGetInputDevice },
+    { "nativeGetInputDeviceIds", "(I)[I",
+            (void*) nativeGetInputDeviceIds },
+    { "nativeGetInputConfiguration", "(ILandroid/content/res/Configuration;)V",
+            (void*) nativeGetInputConfiguration },
+    { "nativeTransferTouchFocus", "(ILandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
+            (void*) nativeTransferTouchFocus },
+    { "nativeSetPointerSpeed", "(II)V",
+            (void*) nativeSetPointerSpeed },
+    { "nativeSetShowTouches", "(IZ)V",
+            (void*) nativeSetShowTouches },
+    { "nativeDump", "(I)Ljava/lang/String;",
+            (void*) nativeDump },
+    { "nativeMonitor", "(I)V",
+            (void*) nativeMonitor },
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_server_InputManager(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "com/android/server/input/InputManagerService",
+            gInputManagerMethods, NELEM(gInputManagerMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    // Callbacks
+
+    jclass clazz;
+    FIND_CLASS(clazz, "com/android/server/input/InputManagerService");
+
+    GET_METHOD_ID(gServiceClassInfo.notifyConfigurationChanged, clazz,
+            "notifyConfigurationChanged", "(J)V");
+
+    GET_METHOD_ID(gServiceClassInfo.notifyLidSwitchChanged, clazz,
+            "notifyLidSwitchChanged", "(JZ)V");
+
+    GET_METHOD_ID(gServiceClassInfo.notifyInputChannelBroken, clazz,
+            "notifyInputChannelBroken", "(Lcom/android/server/input/InputWindowHandle;)V");
+
+    GET_METHOD_ID(gServiceClassInfo.notifyANR, clazz,
+            "notifyANR",
+            "(Lcom/android/server/input/InputApplicationHandle;Lcom/android/server/input/InputWindowHandle;)J");
+
+    GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz,
+            "filterInputEvent", "(Landroid/view/InputEvent;I)Z");
+
+    GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeQueueing, clazz,
+            "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;IZ)I");
+
+    GET_METHOD_ID(gServiceClassInfo.interceptMotionBeforeQueueingWhenScreenOff,
+            clazz,
+            "interceptMotionBeforeQueueingWhenScreenOff", "(I)I");
+
+    GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeDispatching, clazz,
+            "interceptKeyBeforeDispatching",
+            "(Lcom/android/server/input/InputWindowHandle;Landroid/view/KeyEvent;I)J");
+
+    GET_METHOD_ID(gServiceClassInfo.dispatchUnhandledKey, clazz,
+            "dispatchUnhandledKey",
+            "(Lcom/android/server/input/InputWindowHandle;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
+
+    GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
+            "checkInjectEventsPermission", "(II)Z");
+
+    GET_METHOD_ID(gServiceClassInfo.getVirtualKeyQuietTimeMillis, clazz,
+            "getVirtualKeyQuietTimeMillis", "()I");
+
+    GET_METHOD_ID(gServiceClassInfo.getExcludedDeviceNames, clazz,
+            "getExcludedDeviceNames", "()[Ljava/lang/String;");
+
+    GET_METHOD_ID(gServiceClassInfo.getKeyRepeatTimeout, clazz,
+            "getKeyRepeatTimeout", "()I");
+
+    GET_METHOD_ID(gServiceClassInfo.getKeyRepeatDelay, clazz,
+            "getKeyRepeatDelay", "()I");
+
+    GET_METHOD_ID(gServiceClassInfo.getHoverTapTimeout, clazz,
+            "getHoverTapTimeout", "()I");
+
+    GET_METHOD_ID(gServiceClassInfo.getHoverTapSlop, clazz,
+            "getHoverTapSlop", "()I");
+
+    GET_METHOD_ID(gServiceClassInfo.getDoubleTapTimeout, clazz,
+            "getDoubleTapTimeout", "()I");
+
+    GET_METHOD_ID(gServiceClassInfo.getLongPressTimeout, clazz,
+            "getLongPressTimeout", "()I");
+
+    GET_METHOD_ID(gServiceClassInfo.getPointerLayer, clazz,
+            "getPointerLayer", "()I");
+
+    GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
+            "getPointerIcon", "()Landroid/view/PointerIcon;");
+
+    // KeyEvent
+
+    FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent");
+    gKeyEventClassInfo.clazz = jclass(env->NewGlobalRef(gKeyEventClassInfo.clazz));
+
+    // MotionEvent
+
+    FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent");
+    gMotionEventClassInfo.clazz = jclass(env->NewGlobalRef(gMotionEventClassInfo.clazz));
+
+    // InputDevice
+
+    FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
+    gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
+
+    GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
+            "<init>", "()V");
+
+    GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
+            "addMotionRange", "(IIFFFF)V");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mId, gInputDeviceClassInfo.clazz,
+            "mId", "I");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mName, gInputDeviceClassInfo.clazz,
+            "mName", "Ljava/lang/String;");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mSources, gInputDeviceClassInfo.clazz,
+            "mSources", "I");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mKeyboardType, gInputDeviceClassInfo.clazz,
+            "mKeyboardType", "I");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mKeyCharacterMapFile, gInputDeviceClassInfo.clazz,
+            "mKeyCharacterMapFile", "Ljava/lang/String;");
+
+    // Configuration
+
+    FIND_CLASS(clazz, "android/content/res/Configuration");
+
+    GET_FIELD_ID(gConfigurationClassInfo.touchscreen, clazz,
+            "touchscreen", "I");
+
+    GET_FIELD_ID(gConfigurationClassInfo.keyboard, clazz,
+            "keyboard", "I");
+
+    GET_FIELD_ID(gConfigurationClassInfo.navigation, clazz,
+            "navigation", "I");
+
+    return 0;
+}
+
+} /* namespace android */