/*
 * 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

// Log debug messages about input focus tracking
#define DEBUG_FOCUS 0

#include "JNIHelp.h"
#include "jni.h"
#include <limits.h>
#include <android_runtime/AndroidRuntime.h>
#include <ui/InputReader.h>
#include <ui/InputDispatcher.h>
#include <ui/InputManager.h>
#include <ui/InputTransport.h>
#include <utils/Log.h>
#include <utils/threads.h>
#include "../../core/jni/android_view_KeyEvent.h"
#include "../../core/jni/android_view_MotionEvent.h"
#include "../../core/jni/android_view_InputChannel.h"
#include "com_android_server_PowerManagerService.h"

namespace android {

// Window flags from WindowManager.LayoutParams
enum {
    FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001,
    FLAG_DIM_BEHIND        = 0x00000002,
    FLAG_BLUR_BEHIND        = 0x00000004,
    FLAG_NOT_FOCUSABLE      = 0x00000008,
    FLAG_NOT_TOUCHABLE      = 0x00000010,
    FLAG_NOT_TOUCH_MODAL    = 0x00000020,
    FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
    FLAG_KEEP_SCREEN_ON     = 0x00000080,
    FLAG_LAYOUT_IN_SCREEN   = 0x00000100,
    FLAG_LAYOUT_NO_LIMITS   = 0x00000200,
    FLAG_FULLSCREEN      = 0x00000400,
    FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800,
    FLAG_DITHER             = 0x00001000,
    FLAG_SECURE             = 0x00002000,
    FLAG_SCALED             = 0x00004000,
    FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000,
    FLAG_LAYOUT_INSET_DECOR = 0x00010000,
    FLAG_ALT_FOCUSABLE_IM = 0x00020000,
    FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
    FLAG_SHOW_WHEN_LOCKED = 0x00080000,
    FLAG_SHOW_WALLPAPER = 0x00100000,
    FLAG_TURN_SCREEN_ON = 0x00200000,
    FLAG_DISMISS_KEYGUARD = 0x00400000,
    FLAG_IMMERSIVE = 0x00800000,
    FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000,
    FLAG_COMPATIBLE_WINDOW = 0x20000000,
    FLAG_SYSTEM_ERROR = 0x40000000,
};

// Window types from WindowManager.LayoutParams
enum {
    FIRST_APPLICATION_WINDOW = 1,
    TYPE_BASE_APPLICATION   = 1,
    TYPE_APPLICATION        = 2,
    TYPE_APPLICATION_STARTING = 3,
    LAST_APPLICATION_WINDOW = 99,
    FIRST_SUB_WINDOW        = 1000,
    TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW,
    TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1,
    TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2,
    TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3,
    TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4,
    LAST_SUB_WINDOW         = 1999,
    FIRST_SYSTEM_WINDOW     = 2000,
    TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW,
    TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1,
    TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2,
    TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3,
    TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4,
    TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5,
    TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6,
    TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7,
    TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8,
    TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9,
    TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10,
    TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11,
    TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12,
    TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13,
    TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14,
    LAST_SYSTEM_WINDOW      = 2999,
};

// Delay between reporting long touch events to the power manager.
const nsecs_t EVENT_IGNORE_DURATION = 300 * 1000000LL; // 300 ms

// Default input dispatching timeout if there is no focused application or paused window
// from which to determine an appropriate dispatching timeout.
const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec

// Minimum amount of time to provide to the input dispatcher for delivery of an event
// regardless of how long the application window was paused.
const nsecs_t MIN_INPUT_DISPATCHING_TIMEOUT = 1000 * 1000000LL; // 1 sec

// ----------------------------------------------------------------------------

static struct {
    jclass clazz;

    jmethodID notifyConfigurationChanged;
    jmethodID notifyLidSwitchChanged;
    jmethodID notifyInputChannelBroken;
    jmethodID notifyInputChannelANR;
    jmethodID notifyInputChannelRecoveredFromANR;
    jmethodID notifyANR;
    jmethodID virtualKeyDownFeedback;
    jmethodID interceptKeyBeforeQueueing;
    jmethodID interceptKeyBeforeDispatching;
    jmethodID checkInjectEventsPermission;
    jmethodID notifyAppSwitchComing;
    jmethodID filterTouchEvents;
    jmethodID filterJumpyTouchEvents;
    jmethodID getVirtualKeyDefinitions;
    jmethodID getInputDeviceCalibration;
    jmethodID getExcludedDeviceNames;
    jmethodID getMaxEventsPerSecond;
} gCallbacksClassInfo;

static struct {
    jclass clazz;

    jfieldID scanCode;
    jfieldID centerX;
    jfieldID centerY;
    jfieldID width;
    jfieldID height;
} gVirtualKeyDefinitionClassInfo;

static struct {
    jclass clazz;

    jfieldID keys;
    jfieldID values;
} gInputDeviceCalibrationClassInfo;

static struct {
    jclass clazz;

    jfieldID inputChannel;
    jfieldID layoutParamsFlags;
    jfieldID layoutParamsType;
    jfieldID dispatchingTimeoutNanos;
    jfieldID frameLeft;
    jfieldID frameTop;
    jfieldID frameRight;
    jfieldID frameBottom;
    jfieldID visibleFrameLeft;
    jfieldID visibleFrameTop;
    jfieldID visibleFrameRight;
    jfieldID visibleFrameBottom;
    jfieldID touchableAreaLeft;
    jfieldID touchableAreaTop;
    jfieldID touchableAreaRight;
    jfieldID touchableAreaBottom;
    jfieldID visible;
    jfieldID hasFocus;
    jfieldID hasWallpaper;
    jfieldID paused;
    jfieldID ownerPid;
    jfieldID ownerUid;
} gInputWindowClassInfo;

static struct {
    jclass clazz;

    jfieldID name;
    jfieldID dispatchingTimeoutNanos;
    jfieldID token;
} gInputApplicationClassInfo;

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 mMotionRanges;
} gInputDeviceClassInfo;

// ----------------------------------------------------------------------------

static inline nsecs_t now() {
    return systemTime(SYSTEM_TIME_MONOTONIC);
}

// ----------------------------------------------------------------------------

class NativeInputManager : public virtual RefBase,
    public virtual InputReaderPolicyInterface,
    public virtual InputDispatcherPolicyInterface {
protected:
    virtual ~NativeInputManager();

public:
    NativeInputManager(jobject callbacksObj);

    inline sp<InputManager> getInputManager() const { return mInputManager; }

    String8 dump();

    void setDisplaySize(int32_t displayId, int32_t width, int32_t height);
    void setDisplayOrientation(int32_t displayId, int32_t orientation);

    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
            jweak inputChannelObjWeak, bool monitor);
    status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);

    void setInputWindows(JNIEnv* env, jobjectArray windowObjArray);
    void setFocusedApplication(JNIEnv* env, jobject applicationObj);
    void setInputDispatchMode(bool enabled, bool frozen);
    void preemptInputDispatch();

    /* --- InputReaderPolicyInterface implementation --- */

    virtual bool getDisplayInfo(int32_t displayId,
            int32_t* width, int32_t* height, int32_t* orientation);
    virtual void virtualKeyDownFeedback();
    virtual int32_t interceptKey(nsecs_t when, int32_t deviceId,
            bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags);
    virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
            uint32_t& policyFlags);
    virtual int32_t interceptGeneric(nsecs_t when, uint32_t& policyFlags);
    virtual bool filterTouchEvents();
    virtual bool filterJumpyTouchEvents();
    virtual void getVirtualKeyDefinitions(const String8& deviceName,
            Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions);
    virtual void getInputDeviceCalibration(const String8& deviceName,
            InputDeviceCalibration& outCalibration);
    virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);

    /* --- InputDispatcherPolicyInterface implementation --- */

    virtual void notifyConfigurationChanged(nsecs_t when);
    virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel);
    virtual bool notifyInputChannelANR(const sp<InputChannel>& inputChannel,
            nsecs_t& outNewTimeout);
    virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel);
    virtual nsecs_t getKeyRepeatTimeout();
    virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
    virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
    virtual int32_t getMaxEventsPerSecond();

private:
    struct InputWindow {
        sp<InputChannel> inputChannel;
        int32_t layoutParamsFlags;
        int32_t layoutParamsType;
        nsecs_t dispatchingTimeout;
        int32_t frameLeft;
        int32_t frameTop;
        int32_t frameRight;
        int32_t frameBottom;
        int32_t visibleFrameLeft;
        int32_t visibleFrameTop;
        int32_t visibleFrameRight;
        int32_t visibleFrameBottom;
        int32_t touchableAreaLeft;
        int32_t touchableAreaTop;
        int32_t touchableAreaRight;
        int32_t touchableAreaBottom;
        bool visible;
        bool hasFocus;
        bool hasWallpaper;
        bool paused;
        int32_t ownerPid;
        int32_t ownerUid;

        bool visibleFrameIntersects(const InputWindow* other) const;
        bool touchableAreaContainsPoint(int32_t x, int32_t y) const;
    };

    struct InputApplication {
        String8 name;
        nsecs_t dispatchingTimeout;
        jweak tokenObjWeak;
    };

    class ANRTimer {
        enum Budget {
            SYSTEM = 0,
            APPLICATION = 1
        };

        Budget mBudget;
        nsecs_t mStartTime;
        bool mFrozen;
        InputWindow* mPausedWindow;

    public:
        ANRTimer();

        void dispatchFrozenBySystem();
        void dispatchPausedByApplication(InputWindow* pausedWindow);
        bool waitForDispatchStateChangeLd(NativeInputManager* inputManager);

        nsecs_t getTimeSpentWaitingForApplication() const;
    };

    sp<InputManager> mInputManager;

    jobject mCallbacksObj;

    // Cached filtering policies.
    int32_t mFilterTouchEvents;
    int32_t mFilterJumpyTouchEvents;

    // Cached throttling policy.
    int32_t mMaxEventsPerSecond;

    // Cached display state.  (lock mDisplayLock)
    Mutex mDisplayLock;
    int32_t mDisplayWidth, mDisplayHeight;
    int32_t mDisplayOrientation;

    // Power manager interactions.
    bool isScreenOn();
    bool isScreenBright();

    // Weak references to all currently registered input channels by connection pointer.
    Mutex mInputChannelRegistryLock;
    KeyedVector<InputChannel*, jweak> mInputChannelObjWeakTable;

    jobject getInputChannelObjLocal(JNIEnv* env, const sp<InputChannel>& inputChannel);

    // Input target and focus tracking.  (lock mDispatchLock)
    Mutex mDispatchLock;
    Condition mDispatchStateChanged;

    bool mDispatchEnabled;
    bool mDispatchFrozen;
    bool mWindowsReady;
    Vector<InputWindow> mWindows;
    Vector<InputWindow*> mWallpaperWindows;
    Vector<sp<InputChannel> > mMonitoringChannels;

    // Focus tracking for keys, trackball, etc.
    InputWindow* mFocusedWindow;

    // Focus tracking for touch.
    bool mTouchDown;
    InputWindow* mTouchedWindow;                   // primary target for current down
    bool mTouchedWindowIsObscured;                 // true if other windows may obscure the target
    Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets
    struct OutsideTarget {
        InputWindow* window;
        bool obscured;
    };
    Vector<OutsideTarget> mTempTouchedOutsideTargets; // temporary outside touch targets
    Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets

    // Focused application.
    InputApplication* mFocusedApplication;
    InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication

    void dumpDeviceInfo(String8& dump);
    void dumpDispatchStateLd(String8& dump);
    void logDispatchStateLd();

    bool notifyANR(jobject tokenObj, nsecs_t& outNewTimeout);
    void releaseFocusedApplicationLd(JNIEnv* env);

    int32_t waitForFocusedWindowLd(uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid,
            Vector<InputTarget>& outTargets, InputWindow*& outFocusedWindow);
    int32_t waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags,
            int32_t injectorPid, int32_t injectorUid,
            Vector<InputTarget>& outTargets, InputWindow*& outTouchedWindow);
    bool isWindowObscuredLocked(const InputWindow* window);

    void releaseTouchedWindowLd();

    int32_t waitForNonTouchEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
    int32_t waitForTouchEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);

    bool interceptKeyBeforeDispatching(const InputTarget& target,
            const KeyEvent* keyEvent, uint32_t policyFlags);

    void pokeUserActivityIfNeeded(int32_t windowType, int32_t eventType);
    void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
    bool checkInjectionPermission(const InputWindow* window,
            int32_t injectorPid, int32_t injectorUid);

    static bool populateWindow(JNIEnv* env, jobject windowObj, InputWindow& outWindow);
    static void addTarget(const InputWindow* window, int32_t targetFlags,
            nsecs_t timeSpentWaitingForApplication, Vector<InputTarget>& outTargets);

    void registerMonitoringChannel(const sp<InputChannel>& inputChannel);
    void unregisterMonitoringChannel(const sp<InputChannel>& inputChannel);
    void addMonitoringTargetsLd(Vector<InputTarget>& outTargets);

    static inline JNIEnv* jniEnv() {
        return AndroidRuntime::getJNIEnv();
    }

    static bool isAppSwitchKey(int32_t keyCode);
    static bool isPolicyKey(int32_t keyCode, bool isScreenOn);
    static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
};

// ----------------------------------------------------------------------------

NativeInputManager::NativeInputManager(jobject callbacksObj) :
    mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1),
    mMaxEventsPerSecond(-1),
    mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0),
    mDispatchEnabled(true), mDispatchFrozen(false), mWindowsReady(true),
    mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL),
    mFocusedApplication(NULL) {
    JNIEnv* env = jniEnv();

    mCallbacksObj = env->NewGlobalRef(callbacksObj);

    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

NativeInputManager::~NativeInputManager() {
    JNIEnv* env = jniEnv();

    env->DeleteGlobalRef(mCallbacksObj);

    releaseFocusedApplicationLd(env);
}

String8 NativeInputManager::dump() {
    String8 dump;
    { // acquire lock
        AutoMutex _l(mDisplayLock);
        dump.append("Native Input Dispatcher State:\n");
        dumpDispatchStateLd(dump);
        dump.append("\n");
    } // release lock

    dump.append("Input Devices:\n");
    dumpDeviceInfo(dump);

    return dump;
}

bool NativeInputManager::isAppSwitchKey(int32_t keyCode) {
    return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL;
}

bool NativeInputManager::isPolicyKey(int32_t keyCode, bool isScreenOn) {
    // Special keys that the WindowManagerPolicy might care about.
    switch (keyCode) {
    case AKEYCODE_VOLUME_UP:
    case AKEYCODE_VOLUME_DOWN:
    case AKEYCODE_ENDCALL:
    case AKEYCODE_POWER:
    case AKEYCODE_CALL:
    case AKEYCODE_HOME:
    case AKEYCODE_MENU:
    case AKEYCODE_SEARCH:
        // media keys
    case AKEYCODE_HEADSETHOOK:
    case AKEYCODE_MEDIA_PLAY_PAUSE:
    case AKEYCODE_MEDIA_STOP:
    case AKEYCODE_MEDIA_NEXT:
    case AKEYCODE_MEDIA_PREVIOUS:
    case AKEYCODE_MEDIA_REWIND:
    case AKEYCODE_MEDIA_FAST_FORWARD:
        return true;
    default:
        // We need to pass all keys to the policy in the following cases:
        // - screen is off
        // - keyguard is visible
        // - policy is performing key chording
        //return ! isScreenOn || keyguardVisible || chording;
        return true; // XXX stubbed out for now
    }
}

bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
    if (env->ExceptionCheck()) {
        LOGE("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) {
    if (displayId == 0) {
        AutoMutex _l(mDisplayLock);

        mDisplayWidth = width;
        mDisplayHeight = height;
    }
}

void NativeInputManager::setDisplayOrientation(int32_t displayId, int32_t orientation) {
    if (displayId == 0) {
        AutoMutex _l(mDisplayLock);

        mDisplayOrientation = orientation;
    }
}

status_t NativeInputManager::registerInputChannel(JNIEnv* env,
        const sp<InputChannel>& inputChannel, jobject inputChannelObj, bool monitor) {
    jweak inputChannelObjWeak = env->NewWeakGlobalRef(inputChannelObj);
    if (! inputChannelObjWeak) {
        LOGE("Could not create weak reference for input channel.");
        LOGE_EX(env);
        return NO_MEMORY;
    }

    status_t status;
    {
        AutoMutex _l(mInputChannelRegistryLock);

        ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannel.get());
        if (index >= 0) {
            LOGE("Input channel object '%s' has already been registered",
                    inputChannel->getName().string());
            status = INVALID_OPERATION;
            goto DeleteWeakRef;
        }

        mInputChannelObjWeakTable.add(inputChannel.get(), inputChannelObjWeak);
    }

    status = mInputManager->registerInputChannel(inputChannel);
    if (! status) {
        // Success.
        if (monitor) {
            registerMonitoringChannel(inputChannel);
        }
        return OK;
    }

    // Failed!
    {
        AutoMutex _l(mInputChannelRegistryLock);
        mInputChannelObjWeakTable.removeItem(inputChannel.get());
    }

DeleteWeakRef:
    env->DeleteWeakGlobalRef(inputChannelObjWeak);
    return status;
}

status_t NativeInputManager::unregisterInputChannel(JNIEnv* env,
        const sp<InputChannel>& inputChannel) {
    jweak inputChannelObjWeak;
    {
        AutoMutex _l(mInputChannelRegistryLock);

        ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannel.get());
        if (index < 0) {
            LOGE("Input channel object '%s' is not currently registered",
                    inputChannel->getName().string());
            return INVALID_OPERATION;
        }

        inputChannelObjWeak = mInputChannelObjWeakTable.valueAt(index);
        mInputChannelObjWeakTable.removeItemsAt(index);
    }

    env->DeleteWeakGlobalRef(inputChannelObjWeak);

    unregisterMonitoringChannel(inputChannel);

    return mInputManager->unregisterInputChannel(inputChannel);
}

jobject NativeInputManager::getInputChannelObjLocal(JNIEnv* env,
        const sp<InputChannel>& inputChannel) {
    {
        AutoMutex _l(mInputChannelRegistryLock);

        ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannel.get());
        if (index < 0) {
            return NULL;
        }

        jweak inputChannelObjWeak = mInputChannelObjWeakTable.valueAt(index);
        return env->NewLocalRef(inputChannelObjWeak);
    }
}

bool NativeInputManager::getDisplayInfo(int32_t displayId,
        int32_t* width, int32_t* height, int32_t* orientation) {
    bool result = false;
    if (displayId == 0) {
        AutoMutex _l(mDisplayLock);

        if (mDisplayWidth > 0) {
            if (width) {
                *width = mDisplayWidth;
            }
            if (height) {
                *height = mDisplayHeight;
            }
            if (orientation) {
                *orientation = mDisplayOrientation;
            }
            result = true;
        }
    }
    return result;
}

bool NativeInputManager::isScreenOn() {
    return android_server_PowerManagerService_isScreenOn();
}

bool NativeInputManager::isScreenBright() {
    return android_server_PowerManagerService_isScreenBright();
}

void NativeInputManager::virtualKeyDownFeedback() {
#if DEBUG_INPUT_READER_POLICY
    LOGD("virtualKeyDownFeedback");
#endif

    JNIEnv* env = jniEnv();

    env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.virtualKeyDownFeedback);
    checkAndClearExceptionFromCallback(env, "virtualKeyDownFeedback");
}

int32_t NativeInputManager::interceptKey(nsecs_t when,
        int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) {
#if DEBUG_INPUT_READER_POLICY
    LOGD("interceptKey - when=%lld, deviceId=%d, down=%d, keyCode=%d, scanCode=%d, "
            "policyFlags=0x%x",
            when, deviceId, down, keyCode, scanCode, policyFlags);
#endif

    const int32_t WM_ACTION_PASS_TO_USER = 1;
    const int32_t WM_ACTION_POKE_USER_ACTIVITY = 2;
    const int32_t WM_ACTION_GO_TO_SLEEP = 4;

    bool isScreenOn = this->isScreenOn();
    bool isScreenBright = this->isScreenBright();

    jint wmActions = 0;
    if (isPolicyKey(keyCode, isScreenOn)) {
        JNIEnv* env = jniEnv();

        wmActions = env->CallIntMethod(mCallbacksObj,
                gCallbacksClassInfo.interceptKeyBeforeQueueing,
                when, keyCode, down, policyFlags, isScreenOn);
        if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
            wmActions = 0;
        }
    } else {
        wmActions = WM_ACTION_PASS_TO_USER;
    }

    int32_t actions = InputReaderPolicyInterface::ACTION_NONE;
    if (! isScreenOn) {
        // Key presses and releases wake the device.
        policyFlags |= POLICY_FLAG_WOKE_HERE;
    }

    if (! isScreenBright) {
        // Key presses and releases brighten the screen if dimmed.
        policyFlags |= POLICY_FLAG_BRIGHT_HERE;
    }

    if (wmActions & WM_ACTION_GO_TO_SLEEP) {
        android_server_PowerManagerService_goToSleep(when);
    }

    if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) {
        pokeUserActivity(when, POWER_MANAGER_BUTTON_EVENT);
    }

    if (wmActions & WM_ACTION_PASS_TO_USER) {
        actions |= InputReaderPolicyInterface::ACTION_DISPATCH;

        if (down && isAppSwitchKey(keyCode)) {
            JNIEnv* env = jniEnv();

            env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyAppSwitchComing);
            checkAndClearExceptionFromCallback(env, "notifyAppSwitchComing");

            actions |= InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING;
        }
    }

    return actions;
}

int32_t NativeInputManager::interceptGeneric(nsecs_t when, uint32_t& policyFlags) {
#if DEBUG_INPUT_READER_POLICY
    LOGD("interceptGeneric - when=%lld, policyFlags=0x%x", when, policyFlags);
#endif

    int32_t actions = InputReaderPolicyInterface::ACTION_NONE;
    if (isScreenOn()) {
        // Only dispatch events when the device is awake.
        // Do not wake the device.
        actions |= InputReaderPolicyInterface::ACTION_DISPATCH;

        if (! isScreenBright()) {
            // Brighten the screen if dimmed.
            policyFlags |= POLICY_FLAG_BRIGHT_HERE;
        }
    }

    return actions;
}

int32_t NativeInputManager::interceptSwitch(nsecs_t when, int32_t switchCode,
        int32_t switchValue, uint32_t& policyFlags) {
#if DEBUG_INPUT_READER_POLICY
    LOGD("interceptSwitch - when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x",
            when, switchCode, switchValue, policyFlags);
#endif

    JNIEnv* env = jniEnv();

    switch (switchCode) {
    case SW_LID:
        env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyLidSwitchChanged,
                when, switchValue == 0);
        checkAndClearExceptionFromCallback(env, "notifyLidSwitchChanged");
        break;
    }

    return InputReaderPolicyInterface::ACTION_NONE;
}

bool NativeInputManager::filterTouchEvents() {
    if (mFilterTouchEvents < 0) {
        JNIEnv* env = jniEnv();

        jboolean result = env->CallBooleanMethod(mCallbacksObj,
                gCallbacksClassInfo.filterTouchEvents);
        if (checkAndClearExceptionFromCallback(env, "filterTouchEvents")) {
            result = false;
        }

        mFilterTouchEvents = result ? 1 : 0;
    }
    return mFilterTouchEvents;
}

bool NativeInputManager::filterJumpyTouchEvents() {
    if (mFilterJumpyTouchEvents < 0) {
        JNIEnv* env = jniEnv();

        jboolean result = env->CallBooleanMethod(mCallbacksObj,
                gCallbacksClassInfo.filterJumpyTouchEvents);
        if (checkAndClearExceptionFromCallback(env, "filterJumpyTouchEvents")) {
            result = false;
        }

        mFilterJumpyTouchEvents = result ? 1 : 0;
    }
    return mFilterJumpyTouchEvents;
}

void NativeInputManager::getVirtualKeyDefinitions(const String8& deviceName,
        Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) {
    outVirtualKeyDefinitions.clear();

    JNIEnv* env = jniEnv();

    jstring deviceNameStr = env->NewStringUTF(deviceName.string());
    if (! checkAndClearExceptionFromCallback(env, "getVirtualKeyDefinitions")) {
        jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj,
                gCallbacksClassInfo.getVirtualKeyDefinitions, deviceNameStr));
        if (! checkAndClearExceptionFromCallback(env, "getVirtualKeyDefinitions") && result) {
            jsize length = env->GetArrayLength(result);
            for (jsize i = 0; i < length; i++) {
                jobject item = env->GetObjectArrayElement(result, i);

                outVirtualKeyDefinitions.add();
                outVirtualKeyDefinitions.editTop().scanCode =
                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.scanCode));
                outVirtualKeyDefinitions.editTop().centerX =
                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerX));
                outVirtualKeyDefinitions.editTop().centerY =
                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerY));
                outVirtualKeyDefinitions.editTop().width =
                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.width));
                outVirtualKeyDefinitions.editTop().height =
                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.height));

                env->DeleteLocalRef(item);
            }
            env->DeleteLocalRef(result);
        }
        env->DeleteLocalRef(deviceNameStr);
    }
}

void NativeInputManager::getInputDeviceCalibration(const String8& deviceName,
        InputDeviceCalibration& outCalibration) {
    outCalibration.clear();

    JNIEnv* env = jniEnv();

    jstring deviceNameStr = env->NewStringUTF(deviceName.string());
    if (! checkAndClearExceptionFromCallback(env, "getInputDeviceCalibration")) {
        jobject result = env->CallObjectMethod(mCallbacksObj,
                gCallbacksClassInfo.getInputDeviceCalibration, deviceNameStr);
        if (! checkAndClearExceptionFromCallback(env, "getInputDeviceCalibration") && result) {
            jobjectArray keys = jobjectArray(env->GetObjectField(result,
                    gInputDeviceCalibrationClassInfo.keys));
            jobjectArray values = jobjectArray(env->GetObjectField(result,
                    gInputDeviceCalibrationClassInfo.values));

            jsize length = env->GetArrayLength(keys);
            for (jsize i = 0; i < length; i++) {
                jstring keyStr = jstring(env->GetObjectArrayElement(keys, i));
                jstring valueStr = jstring(env->GetObjectArrayElement(values, i));

                const char* keyChars = env->GetStringUTFChars(keyStr, NULL);
                String8 key(keyChars);
                env->ReleaseStringUTFChars(keyStr, keyChars);

                const char* valueChars = env->GetStringUTFChars(valueStr, NULL);
                String8 value(valueChars);
                env->ReleaseStringUTFChars(valueStr, valueChars);

                outCalibration.addProperty(key, value);

                env->DeleteLocalRef(keyStr);
                env->DeleteLocalRef(valueStr);
            }
            env->DeleteLocalRef(keys);
            env->DeleteLocalRef(values);
            env->DeleteLocalRef(result);
        }
        env->DeleteLocalRef(deviceNameStr);
    }
}

void NativeInputManager::getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) {
    outExcludedDeviceNames.clear();

    JNIEnv* env = jniEnv();

    jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj,
            gCallbacksClassInfo.getExcludedDeviceNames));
    if (! checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && result) {
        jsize length = env->GetArrayLength(result);
        for (jsize i = 0; i < length; i++) {
            jstring item = jstring(env->GetObjectArrayElement(result, i));

            const char* deviceNameChars = env->GetStringUTFChars(item, NULL);
            outExcludedDeviceNames.add(String8(deviceNameChars));
            env->ReleaseStringUTFChars(item, deviceNameChars);

            env->DeleteLocalRef(item);
        }
        env->DeleteLocalRef(result);
    }
}

void NativeInputManager::notifyConfigurationChanged(nsecs_t when) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("notifyConfigurationChanged - when=%lld", when);
#endif

    JNIEnv* env = jniEnv();

    InputConfiguration config;
    mInputManager->getInputConfiguration(& config);

    env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyConfigurationChanged,
            when, config.touchScreen, config.keyboard, config.navigation);
    checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged");
}

void NativeInputManager::notifyInputChannelBroken(const sp<InputChannel>& inputChannel) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("notifyInputChannelBroken - inputChannel='%s'", inputChannel->getName().string());
#endif

    JNIEnv* env = jniEnv();

    jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
    if (inputChannelObjLocal) {
        env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyInputChannelBroken,
                inputChannelObjLocal);
        checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");

        env->DeleteLocalRef(inputChannelObjLocal);
    }

    unregisterMonitoringChannel(inputChannel);
}

bool NativeInputManager::notifyInputChannelANR(const sp<InputChannel>& inputChannel,
        nsecs_t& outNewTimeout) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("notifyInputChannelANR - inputChannel='%s'",
            inputChannel->getName().string());
#endif

    JNIEnv* env = jniEnv();

    jlong newTimeout;
    jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
    if (inputChannelObjLocal) {
        newTimeout = env->CallLongMethod(mCallbacksObj,
                gCallbacksClassInfo.notifyInputChannelANR, inputChannelObjLocal);
        if (checkAndClearExceptionFromCallback(env, "notifyInputChannelANR")) {
            newTimeout = -2;
        }

        env->DeleteLocalRef(inputChannelObjLocal);
    } else {
        newTimeout = -2;
    }

    if (newTimeout == -2) {
        return false; // abort
    }

    outNewTimeout = newTimeout;
    return true; // resume
}

void NativeInputManager::notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("notifyInputChannelRecoveredFromANR - inputChannel='%s'",
            inputChannel->getName().string());
#endif

    JNIEnv* env = jniEnv();

    jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
    if (inputChannelObjLocal) {
        env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyInputChannelRecoveredFromANR,
                inputChannelObjLocal);
        checkAndClearExceptionFromCallback(env, "notifyInputChannelRecoveredFromANR");

        env->DeleteLocalRef(inputChannelObjLocal);
    }
}

bool NativeInputManager::notifyANR(jobject tokenObj, nsecs_t& outNewTimeout) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("notifyANR");
#endif

    JNIEnv* env = jniEnv();

    jlong newTimeout = env->CallLongMethod(mCallbacksObj,
            gCallbacksClassInfo.notifyANR, tokenObj);
    if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
        newTimeout = -2;
    }

    if (newTimeout == -2) {
        return false; // abort
    }

    outNewTimeout = newTimeout;
    return true; // resume
}

nsecs_t NativeInputManager::getKeyRepeatTimeout() {
    if (! isScreenOn()) {
        // Disable key repeat when the screen is off.
        return -1;
    } else {
        // TODO use ViewConfiguration.getLongPressTimeout()
        return milliseconds_to_nanoseconds(500);
    }
}

int32_t NativeInputManager::getMaxEventsPerSecond() {
    if (mMaxEventsPerSecond < 0) {
        JNIEnv* env = jniEnv();

        jint result = env->CallIntMethod(mCallbacksObj,
                gCallbacksClassInfo.getMaxEventsPerSecond);
        if (checkAndClearExceptionFromCallback(env, "getMaxEventsPerSecond")) {
            result = 60;
        }

        mMaxEventsPerSecond = result;
    }
    return mMaxEventsPerSecond;
}

void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArray) {
#if DEBUG_FOCUS
    LOGD("setInputWindows");
#endif
    { // acquire lock
        AutoMutex _l(mDispatchLock);

        sp<InputChannel> touchedWindowChannel;
        if (mTouchedWindow) {
            touchedWindowChannel = mTouchedWindow->inputChannel;
            mTouchedWindow = NULL;
        }
        size_t numTouchedWallpapers = mTouchedWallpaperWindows.size();
        if (numTouchedWallpapers != 0) {
            for (size_t i = 0; i < numTouchedWallpapers; i++) {
                mTempTouchedWallpaperChannels.push(mTouchedWallpaperWindows[i]->inputChannel);
            }
            mTouchedWallpaperWindows.clear();
        }

        bool hadFocusedWindow = mFocusedWindow != NULL;

        mWindows.clear();
        mFocusedWindow = NULL;
        mWallpaperWindows.clear();

        if (windowObjArray) {
            mWindowsReady = true;

            jsize length = env->GetArrayLength(windowObjArray);
            for (jsize i = 0; i < length; i++) {
                jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i);
                if (! inputTargetObj) {
                    break; // found null element indicating end of used portion of the array
                }

                mWindows.push();
                InputWindow& window = mWindows.editTop();
                bool valid = populateWindow(env, inputTargetObj, window);
                if (! valid) {
                    mWindows.pop();
                }

                env->DeleteLocalRef(inputTargetObj);
            }

            size_t numWindows = mWindows.size();
            for (size_t i = 0; i < numWindows; i++) {
                InputWindow* window = & mWindows.editItemAt(i);
                if (window->hasFocus) {
                    mFocusedWindow = window;
                }

                if (window->layoutParamsType == TYPE_WALLPAPER) {
                    mWallpaperWindows.push(window);

                    for (size_t j = 0; j < numTouchedWallpapers; j++) {
                        if (window->inputChannel == mTempTouchedWallpaperChannels[i]) {
                            mTouchedWallpaperWindows.push(window);
                        }
                    }
                }

                if (window->inputChannel == touchedWindowChannel) {
                    mTouchedWindow = window;
                }
            }
        } else {
            mWindowsReady = false;
        }

        mTempTouchedWallpaperChannels.clear();

        if ((hadFocusedWindow && ! mFocusedWindow)
                || (mFocusedWindow && ! mFocusedWindow->visible)) {
            preemptInputDispatch();
        }

        mDispatchStateChanged.broadcast();

#if DEBUG_FOCUS
        logDispatchStateLd();
#endif
    } // release lock
}

bool NativeInputManager::populateWindow(JNIEnv* env, jobject windowObj,
        InputWindow& outWindow) {
    bool valid = false;

    jobject inputChannelObj = env->GetObjectField(windowObj,
            gInputWindowClassInfo.inputChannel);
    if (inputChannelObj) {
        sp<InputChannel> inputChannel =
                android_view_InputChannel_getInputChannel(env, inputChannelObj);
        if (inputChannel != NULL) {
            jint layoutParamsFlags = env->GetIntField(windowObj,
                    gInputWindowClassInfo.layoutParamsFlags);
            jint layoutParamsType = env->GetIntField(windowObj,
                    gInputWindowClassInfo.layoutParamsType);
            jlong dispatchingTimeoutNanos = env->GetLongField(windowObj,
                    gInputWindowClassInfo.dispatchingTimeoutNanos);
            jint frameLeft = env->GetIntField(windowObj,
                    gInputWindowClassInfo.frameLeft);
            jint frameTop = env->GetIntField(windowObj,
                    gInputWindowClassInfo.frameTop);
            jint frameRight = env->GetIntField(windowObj,
                    gInputWindowClassInfo.frameRight);
            jint frameBottom = env->GetIntField(windowObj,
                    gInputWindowClassInfo.frameBottom);
            jint visibleFrameLeft = env->GetIntField(windowObj,
                    gInputWindowClassInfo.visibleFrameLeft);
            jint visibleFrameTop = env->GetIntField(windowObj,
                    gInputWindowClassInfo.visibleFrameTop);
            jint visibleFrameRight = env->GetIntField(windowObj,
                    gInputWindowClassInfo.visibleFrameRight);
            jint visibleFrameBottom = env->GetIntField(windowObj,
                    gInputWindowClassInfo.visibleFrameBottom);
            jint touchableAreaLeft = env->GetIntField(windowObj,
                    gInputWindowClassInfo.touchableAreaLeft);
            jint touchableAreaTop = env->GetIntField(windowObj,
                    gInputWindowClassInfo.touchableAreaTop);
            jint touchableAreaRight = env->GetIntField(windowObj,
                    gInputWindowClassInfo.touchableAreaRight);
            jint touchableAreaBottom = env->GetIntField(windowObj,
                    gInputWindowClassInfo.touchableAreaBottom);
            jboolean visible = env->GetBooleanField(windowObj,
                    gInputWindowClassInfo.visible);
            jboolean hasFocus = env->GetBooleanField(windowObj,
                    gInputWindowClassInfo.hasFocus);
            jboolean hasWallpaper = env->GetBooleanField(windowObj,
                    gInputWindowClassInfo.hasWallpaper);
            jboolean paused = env->GetBooleanField(windowObj,
                    gInputWindowClassInfo.paused);
            jint ownerPid = env->GetIntField(windowObj,
                    gInputWindowClassInfo.ownerPid);
            jint ownerUid = env->GetIntField(windowObj,
                    gInputWindowClassInfo.ownerUid);

            outWindow.inputChannel = inputChannel;
            outWindow.layoutParamsFlags = layoutParamsFlags;
            outWindow.layoutParamsType = layoutParamsType;
            outWindow.dispatchingTimeout = dispatchingTimeoutNanos;
            outWindow.frameLeft = frameLeft;
            outWindow.frameTop = frameTop;
            outWindow.frameRight = frameRight;
            outWindow.frameBottom = frameBottom;
            outWindow.visibleFrameLeft = visibleFrameLeft;
            outWindow.visibleFrameTop = visibleFrameTop;
            outWindow.visibleFrameRight = visibleFrameRight;
            outWindow.visibleFrameBottom = visibleFrameBottom;
            outWindow.touchableAreaLeft = touchableAreaLeft;
            outWindow.touchableAreaTop = touchableAreaTop;
            outWindow.touchableAreaRight = touchableAreaRight;
            outWindow.touchableAreaBottom = touchableAreaBottom;
            outWindow.visible = visible;
            outWindow.hasFocus = hasFocus;
            outWindow.hasWallpaper = hasWallpaper;
            outWindow.paused = paused;
            outWindow.ownerPid = ownerPid;
            outWindow.ownerUid = ownerUid;
            valid = true;
        } else {
            LOGW("Dropping input target because its input channel is not initialized.");
        }

        env->DeleteLocalRef(inputChannelObj);
    } else {
        LOGW("Dropping input target because the input channel object was null.");
    }
    return valid;
}

void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationObj) {
#if DEBUG_FOCUS
    LOGD("setFocusedApplication");
#endif
    { // acquire lock
        AutoMutex _l(mDispatchLock);

        releaseFocusedApplicationLd(env);

        if (applicationObj) {
            jstring nameObj = jstring(env->GetObjectField(applicationObj,
                    gInputApplicationClassInfo.name));
            jlong dispatchingTimeoutNanos = env->GetLongField(applicationObj,
                    gInputApplicationClassInfo.dispatchingTimeoutNanos);
            jobject tokenObj = env->GetObjectField(applicationObj,
                    gInputApplicationClassInfo.token);
            jweak tokenObjWeak = env->NewWeakGlobalRef(tokenObj);
            if (! tokenObjWeak) {
                LOGE("Could not create weak reference for application token.");
                LOGE_EX(env);
                env->ExceptionClear();
            }
            env->DeleteLocalRef(tokenObj);

            mFocusedApplication = & mFocusedApplicationStorage;

            if (nameObj) {
                const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
                mFocusedApplication->name.setTo(nameStr);
                env->ReleaseStringUTFChars(nameObj, nameStr);
                env->DeleteLocalRef(nameObj);
            } else {
                LOGE("InputApplication.name should not be null.");
                mFocusedApplication->name.setTo("unknown");
            }

            mFocusedApplication->dispatchingTimeout = dispatchingTimeoutNanos;
            mFocusedApplication->tokenObjWeak = tokenObjWeak;
        }

        mDispatchStateChanged.broadcast();

#if DEBUG_FOCUS
        logDispatchStateLd();
#endif
    } // release lock
}

void NativeInputManager::releaseFocusedApplicationLd(JNIEnv* env) {
    if (mFocusedApplication) {
        env->DeleteWeakGlobalRef(mFocusedApplication->tokenObjWeak);
        mFocusedApplication = NULL;
    }
}

void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) {
#if DEBUG_FOCUS
    LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
#endif

    { // acquire lock
        AutoMutex _l(mDispatchLock);

        if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
            mDispatchEnabled = enabled;
            mDispatchFrozen = frozen;

            mDispatchStateChanged.broadcast();
        }

#if DEBUG_FOCUS
        logDispatchStateLd();
#endif
    } // release lock
}

void NativeInputManager::preemptInputDispatch() {
#if DEBUG_FOCUS
    LOGD("preemptInputDispatch");
#endif

    mInputManager->preemptInputDispatch();
}

int32_t NativeInputManager::waitForFocusedWindowLd(uint32_t policyFlags,
        int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets,
        InputWindow*& outFocusedWindow) {

    int32_t injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
    bool firstIteration = true;
    ANRTimer anrTimer;
    for (;;) {
        if (firstIteration) {
            firstIteration = false;
        } else {
            if (! anrTimer.waitForDispatchStateChangeLd(this)) {
                LOGW("Dropping event because the dispatcher timed out waiting to identify "
                        "the window that should receive it.");
                injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
                break;
            }
        }

        // If dispatch is not enabled then fail.
        if (! mDispatchEnabled) {
            LOGI("Dropping event because input dispatch is disabled.");
            injectionResult = INPUT_EVENT_INJECTION_FAILED;
            break;
        }

        // If dispatch is frozen or we don't have valid window data yet then wait.
        if (mDispatchFrozen || ! mWindowsReady) {
#if DEBUG_FOCUS
            LOGD("Waiting because dispatch is frozen or windows are not ready.");
#endif
            anrTimer.dispatchFrozenBySystem();
            continue;
        }

        // If there is no currently focused window and no focused application
        // then drop the event.
        if (! mFocusedWindow) {
            if (mFocusedApplication) {
#if DEBUG_FOCUS
                LOGD("Waiting because there is no focused window but there is a "
                        "focused application that may yet introduce a new target: '%s'.",
                        mFocusedApplication->name.string());
#endif
                continue;
            }

            LOGI("Dropping event because there is no focused window or focused application.");
            injectionResult = INPUT_EVENT_INJECTION_FAILED;
            break;
        }

        // Check permissions.
        if (! checkInjectionPermission(mFocusedWindow, injectorPid, injectorUid)) {
            injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
            break;
        }

        // If the currently focused window is paused then keep waiting.
        if (mFocusedWindow->paused) {
#if DEBUG_FOCUS
            LOGD("Waiting because focused window is paused.");
#endif
            anrTimer.dispatchPausedByApplication(mFocusedWindow);
            continue;
        }

        // Success!
        break; // done waiting, exit loop
    }

    // Output targets.
    if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
        addTarget(mFocusedWindow, InputTarget::FLAG_SYNC,
                anrTimer.getTimeSpentWaitingForApplication(), outTargets);

        outFocusedWindow = mFocusedWindow;
    } else {
        outFocusedWindow = NULL;
    }

#if DEBUG_FOCUS
    LOGD("waitForFocusedWindow finished: injectionResult=%d",
            injectionResult);
    logDispatchStateLd();
#endif
    return injectionResult;
}

enum InjectionPermission {
    INJECTION_PERMISSION_UNKNOWN,
    INJECTION_PERMISSION_GRANTED,
    INJECTION_PERMISSION_DENIED
};

int32_t NativeInputManager::waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags,
        int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets,
        InputWindow*& outTouchedWindow) {
    nsecs_t startTime = now();

    // For security reasons, we defer updating the touch state until we are sure that
    // event injection will be allowed.
    //
    // FIXME In the original code, screenWasOff could never be set to true.
    //       The reason is that the POLICY_FLAG_WOKE_HERE
    //       and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw
    //       EV_KEY, EV_REL and EV_ABS events.  As it happens, the touch event was
    //       actually enqueued using the policyFlags that appeared in the final EV_SYN
    //       events upon which no preprocessing took place.  So policyFlags was always 0.
    //       In the new native input dispatcher we're a bit more careful about event
    //       preprocessing so the touches we receive can actually have non-zero policyFlags.
    //       Unfortunately we obtain undesirable behavior.
    //
    //       Here's what happens:
    //
    //       When the device dims in anticipation of going to sleep, touches
    //       in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause
    //       the device to brighten and reset the user activity timer.
    //       Touches on other windows (such as the launcher window)
    //       are dropped.  Then after a moment, the device goes to sleep.  Oops.
    //
    //       Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE
    //       instead of POLICY_FLAG_WOKE_HERE...
    //
    bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE;

    int32_t action = motionEvent->getAction();

    bool firstIteration = true;
    ANRTimer anrTimer;
    int32_t injectionResult;
    InjectionPermission injectionPermission;
    for (;;) {
        if (firstIteration) {
            firstIteration = false;
        } else {
            if (! anrTimer.waitForDispatchStateChangeLd(this)) {
                LOGW("Dropping event because the dispatcher timed out waiting to identify "
                        "the window that should receive it.");
                injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
                injectionPermission = INJECTION_PERMISSION_UNKNOWN;
                break; // timed out, exit wait loop
            }
        }

        // If dispatch is not enabled then fail.
        if (! mDispatchEnabled) {
            LOGI("Dropping event because input dispatch is disabled.");
            injectionResult = INPUT_EVENT_INJECTION_FAILED;
            injectionPermission = INJECTION_PERMISSION_UNKNOWN;
            break; // failed, exit wait loop
        }

        // If dispatch is frozen or we don't have valid window data yet then wait.
        if (mDispatchFrozen || ! mWindowsReady) {
#if DEBUG_INPUT_DISPATCHER_POLICY
            LOGD("Waiting because dispatch is frozen or windows are not ready.");
#endif
            anrTimer.dispatchFrozenBySystem();
            continue;
        }

        // Update the touch state as needed based on the properties of the touch event.
        if (action == AMOTION_EVENT_ACTION_DOWN) {
            /* Case 1: ACTION_DOWN */

            InputWindow* newTouchedWindow = NULL;
            mTempTouchedOutsideTargets.clear();

            int32_t x = int32_t(motionEvent->getX(0));
            int32_t y = int32_t(motionEvent->getY(0));
            InputWindow* topErrorWindow = NULL;
            bool obscured = false;

            // Traverse windows from front to back to find touched window and outside targets.
            size_t numWindows = mWindows.size();
            for (size_t i = 0; i < numWindows; i++) {
                InputWindow* window = & mWindows.editItemAt(i);
                int32_t flags = window->layoutParamsFlags;

                if (flags & FLAG_SYSTEM_ERROR) {
                    if (! topErrorWindow) {
                        topErrorWindow = window;
                    }
                }

                if (window->visible) {
                    if (! (flags & FLAG_NOT_TOUCHABLE)) {
                        bool isTouchModal = (flags &
                                (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL)) == 0;
                        if (isTouchModal || window->touchableAreaContainsPoint(x, y)) {
                            if (! screenWasOff || flags & FLAG_TOUCHABLE_WHEN_WAKING) {
                                newTouchedWindow = window;
                                obscured = isWindowObscuredLocked(window);
                            }
                            break; // found touched window, exit window loop
                        }
                    }

                    if (flags & FLAG_WATCH_OUTSIDE_TOUCH) {
                        OutsideTarget outsideTarget;
                        outsideTarget.window = window;
                        outsideTarget.obscured = isWindowObscuredLocked(window);
                        mTempTouchedOutsideTargets.push(outsideTarget);
                    }
                }
            }

            // If there is an error window but it is not taking focus (typically because
            // it is invisible) then wait for it.  Any other focused window may in
            // fact be in ANR state.
            if (topErrorWindow && newTouchedWindow != topErrorWindow) {
#if DEBUG_INPUT_DISPATCHER_POLICY
                LOGD("Waiting because system error window is pending.");
#endif
                anrTimer.dispatchFrozenBySystem();
                continue; // wait some more
            }

            // If we did not find a touched window then fail.
            if (! newTouchedWindow) {
                if (mFocusedApplication) {
#if DEBUG_FOCUS
                    LOGD("Waiting because there is no focused window but there is a "
                            "focused application that may yet introduce a new target: '%s'.",
                            mFocusedApplication->name.string());
#endif
                    continue;
                }

                LOGI("Dropping event because there is no touched window or focused application.");
                injectionResult = INPUT_EVENT_INJECTION_FAILED;
                injectionPermission = INJECTION_PERMISSION_UNKNOWN;
                break; // failed, exit wait loop
            }

            // Check permissions.
            if (! checkInjectionPermission(newTouchedWindow, injectorPid, injectorUid)) {
                injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
                injectionPermission = INJECTION_PERMISSION_DENIED;
                break; // failed, exit wait loop
            }

            // If the touched window is paused then keep waiting.
            if (newTouchedWindow->paused) {
#if DEBUG_INPUT_DISPATCHER_POLICY
                LOGD("Waiting because touched window is paused.");
#endif
                anrTimer.dispatchPausedByApplication(newTouchedWindow);
                continue; // wait some more
            }

            // Success!  Update the touch dispatch state for real.
            releaseTouchedWindowLd();

            mTouchedWindow = newTouchedWindow;
            mTouchedWindowIsObscured = obscured;

            if (newTouchedWindow->hasWallpaper) {
                mTouchedWallpaperWindows.appendVector(mWallpaperWindows);
            }

            injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
            injectionPermission = INJECTION_PERMISSION_GRANTED;
            break; // done
        } else {
            /* Case 2: Everything but ACTION_DOWN */

            // Check permissions.
            if (! checkInjectionPermission(mTouchedWindow, injectorPid, injectorUid)) {
                injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
                injectionPermission = INJECTION_PERMISSION_DENIED;
                break; // failed, exit wait loop
            }

            // If the pointer is not currently down, then ignore the event.
            if (! mTouchDown) {
                LOGI("Dropping event because the pointer is not down.");
                injectionResult = INPUT_EVENT_INJECTION_FAILED;
                injectionPermission = INJECTION_PERMISSION_GRANTED;
                break; // failed, exit wait loop
            }

            // If there is no currently touched window then fail.
            if (! mTouchedWindow) {
#if DEBUG_INPUT_DISPATCHER_POLICY
                LOGD("Dropping event because there is no touched window to receive it.");
#endif
                injectionResult = INPUT_EVENT_INJECTION_FAILED;
                injectionPermission = INJECTION_PERMISSION_GRANTED;
                break; // failed, exit wait loop
            }

            // If the touched window is paused then keep waiting.
            if (mTouchedWindow->paused) {
#if DEBUG_INPUT_DISPATCHER_POLICY
                LOGD("Waiting because touched window is paused.");
#endif
                anrTimer.dispatchPausedByApplication(mTouchedWindow);
                continue; // wait some more
            }

            // Success!
            injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
            injectionPermission = INJECTION_PERMISSION_GRANTED;
            break; // done
        }
    }

    // Output targets.
    if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
        size_t numWallpaperWindows = mTouchedWallpaperWindows.size();
        for (size_t i = 0; i < numWallpaperWindows; i++) {
            addTarget(mTouchedWallpaperWindows[i],
                    InputTarget::FLAG_WINDOW_IS_OBSCURED, 0, outTargets);
        }

        size_t numOutsideTargets = mTempTouchedOutsideTargets.size();
        for (size_t i = 0; i < numOutsideTargets; i++) {
            const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i];
            int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE;
            if (outsideTarget.obscured) {
                outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
            }
            addTarget(outsideTarget.window, outsideTargetFlags, 0, outTargets);
        }

        int32_t targetFlags = InputTarget::FLAG_SYNC;
        if (mTouchedWindowIsObscured) {
            targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
        }
        addTarget(mTouchedWindow, targetFlags,
                anrTimer.getTimeSpentWaitingForApplication(), outTargets);
        outTouchedWindow = mTouchedWindow;
    } else {
        outTouchedWindow = NULL;
    }
    mTempTouchedOutsideTargets.clear();

    // Check injection permission once and for all.
    if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
        if (checkInjectionPermission(action == AMOTION_EVENT_ACTION_DOWN ? NULL : mTouchedWindow,
                injectorPid, injectorUid)) {
            injectionPermission = INJECTION_PERMISSION_GRANTED;
        } else {
            injectionPermission = INJECTION_PERMISSION_DENIED;
        }
    }

    // Update final pieces of touch state if the injector had permission.
    if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
        if (action == AMOTION_EVENT_ACTION_DOWN) {
            if (mTouchDown) {
                // This is weird.  We got a down but we thought it was already down!
                LOGW("Pointer down received while already down.");
            } else {
                mTouchDown = true;
            }

            if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
                // Since we failed to identify a target for this touch down, we may still
                // be holding on to an earlier target from a previous touch down.  Release it.
                releaseTouchedWindowLd();
            }
        } else if (action == AMOTION_EVENT_ACTION_UP) {
            mTouchDown = false;
            releaseTouchedWindowLd();
        }
    } else {
        LOGW("Not updating touch focus because injection was denied.");
    }

#if DEBUG_FOCUS
    LOGD("waitForTouchedWindow finished: injectionResult=%d",
            injectionResult);
    logDispatchStateLd();
#endif
    return injectionResult;
}

void NativeInputManager::releaseTouchedWindowLd() {
    mTouchedWindow = NULL;
    mTouchedWindowIsObscured = false;
    mTouchedWallpaperWindows.clear();
}

void NativeInputManager::addTarget(const InputWindow* window, int32_t targetFlags,
        nsecs_t timeSpentWaitingForApplication, Vector<InputTarget>& outTargets) {
    nsecs_t timeout = window->dispatchingTimeout - timeSpentWaitingForApplication;
    if (timeout < MIN_INPUT_DISPATCHING_TIMEOUT) {
        timeout = MIN_INPUT_DISPATCHING_TIMEOUT;
    }

    outTargets.push();

    InputTarget& target = outTargets.editTop();
    target.inputChannel = window->inputChannel;
    target.flags = targetFlags;
    target.timeout = timeout;
    target.xOffset = - window->frameLeft;
    target.yOffset = - window->frameTop;
}

bool NativeInputManager::checkInjectionPermission(const InputWindow* window,
        int32_t injectorPid, int32_t injectorUid) {
    if (injectorUid > 0 && (window == NULL || window->ownerUid != injectorUid)) {
        JNIEnv* env = jniEnv();
        jboolean result = env->CallBooleanMethod(mCallbacksObj,
                gCallbacksClassInfo.checkInjectEventsPermission, injectorPid, injectorUid);
        checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission");

        if (! result) {
            if (window) {
                LOGW("Permission denied: injecting event from pid %d uid %d to window "
                        "with input channel %s owned by uid %d",
                        injectorPid, injectorUid, window->inputChannel->getName().string(),
                        window->ownerUid);
            } else {
                LOGW("Permission denied: injecting event from pid %d uid %d",
                        injectorPid, injectorUid);
            }
            return false;
        }
    }

    return true;
}

bool NativeInputManager::isWindowObscuredLocked(const InputWindow* window) {
    size_t numWindows = mWindows.size();
    for (size_t i = 0; i < numWindows; i++) {
        const InputWindow* other = & mWindows.itemAt(i);
        if (other == window) {
            break;
        }
        if (other->visible && window->visibleFrameIntersects(other)) {
            return true;
        }
    }
    return false;
}

int32_t NativeInputManager::waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
        int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("waitForKeyEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
            policyFlags, injectorPid, injectorUid);
#endif

    int32_t windowType;
    { // acquire lock
        AutoMutex _l(mDispatchLock);

        InputWindow* focusedWindow;
        int32_t injectionResult = waitForFocusedWindowLd(policyFlags,
                injectorPid, injectorUid, outTargets, /*out*/ focusedWindow);
        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
            return injectionResult;
        }

        windowType = focusedWindow->layoutParamsType;
    } // release lock

    if (isPolicyKey(keyEvent->getKeyCode(), isScreenOn())) {
        const InputTarget& target = outTargets.top();
        bool consumed = interceptKeyBeforeDispatching(target, keyEvent, policyFlags);
        if (consumed) {
            outTargets.clear();
            return INPUT_EVENT_INJECTION_SUCCEEDED;
        }

        addMonitoringTargetsLd(outTargets);
    }

    pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT);
    return INPUT_EVENT_INJECTION_SUCCEEDED;
}

int32_t NativeInputManager::waitForMotionEventTargets(MotionEvent* motionEvent,
        uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid,
        Vector<InputTarget>& outTargets) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("waitForMotionEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
            policyFlags, injectorPid, injectorUid);
#endif

    int32_t source = motionEvent->getSource();
    if (source & AINPUT_SOURCE_CLASS_POINTER) {
        return waitForTouchEventTargets(motionEvent, policyFlags, injectorPid, injectorUid,
                outTargets);
    } else {
        return waitForNonTouchEventTargets(motionEvent, policyFlags, injectorPid, injectorUid,
                outTargets);
    }
}

int32_t NativeInputManager::waitForNonTouchEventTargets(MotionEvent* motionEvent,
        uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid,
        Vector<InputTarget>& outTargets) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("waitForNonTouchEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
            policyFlags, injectorPid, injectorUid);
#endif

    int32_t windowType;
    { // acquire lock
        AutoMutex _l(mDispatchLock);

        InputWindow* focusedWindow;
        int32_t injectionResult = waitForFocusedWindowLd(policyFlags,
                injectorPid, injectorUid, outTargets, /*out*/ focusedWindow);
        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
            return injectionResult;
        }

        windowType = focusedWindow->layoutParamsType;

        addMonitoringTargetsLd(outTargets);
    } // release lock

    pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT);
    return INPUT_EVENT_INJECTION_SUCCEEDED;
}

int32_t NativeInputManager::waitForTouchEventTargets(MotionEvent* motionEvent,
        uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid,
        Vector<InputTarget>& outTargets) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("waitForTouchEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
            policyFlags, injectorPid, injectorUid);
#endif

    int32_t windowType;
    { // acquire lock
        AutoMutex _l(mDispatchLock);

        InputWindow* touchedWindow;
        int32_t injectionResult = waitForTouchedWindowLd(motionEvent, policyFlags,
                injectorPid, injectorUid, outTargets, /*out*/ touchedWindow);
        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
            return injectionResult;
        }

        windowType = touchedWindow->layoutParamsType;

        addMonitoringTargetsLd(outTargets);
    } // release lock

    int32_t eventType;
    switch (motionEvent->getAction()) {
    case AMOTION_EVENT_ACTION_DOWN:
        eventType = POWER_MANAGER_TOUCH_EVENT;
        break;
    case AMOTION_EVENT_ACTION_UP:
        eventType = POWER_MANAGER_TOUCH_UP_EVENT;
        break;
    default:
        if (motionEvent->getEventTime() - motionEvent->getDownTime()
                >= EVENT_IGNORE_DURATION) {
            eventType = POWER_MANAGER_TOUCH_EVENT;
        } else {
            eventType = POWER_MANAGER_LONG_TOUCH_EVENT;
        }
        break;
    }
    pokeUserActivityIfNeeded(windowType, eventType);
    return INPUT_EVENT_INJECTION_SUCCEEDED;
}

bool NativeInputManager::interceptKeyBeforeDispatching(const InputTarget& target,
        const KeyEvent* keyEvent, uint32_t policyFlags) {
    JNIEnv* env = jniEnv();

    jobject inputChannelObj = getInputChannelObjLocal(env, target.inputChannel);
    if (inputChannelObj) {
        jboolean consumed = env->CallBooleanMethod(mCallbacksObj,
                gCallbacksClassInfo.interceptKeyBeforeDispatching,
                inputChannelObj, keyEvent->getAction(), keyEvent->getFlags(),
                keyEvent->getKeyCode(), keyEvent->getMetaState(),
                keyEvent->getRepeatCount(), policyFlags);
        bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");

        env->DeleteLocalRef(inputChannelObj);

        return consumed && ! error;
    } else {
        LOGW("Could not apply key dispatch policy because input channel '%s' is "
                "no longer valid.", target.inputChannel->getName().string());
        return false;
    }
}

void NativeInputManager::pokeUserActivityIfNeeded(int32_t windowType, int32_t eventType) {
    if (windowType != TYPE_KEYGUARD) {
        nsecs_t eventTime = now();
        pokeUserActivity(eventTime, eventType);
    }
}

void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
    android_server_PowerManagerService_userActivity(eventTime, eventType);
}

void NativeInputManager::registerMonitoringChannel(const sp<InputChannel>& inputChannel) {
    { // acquire lock
         AutoMutex _l(mDispatchLock);
         mMonitoringChannels.push(inputChannel);
    } // release lock
}

void NativeInputManager::unregisterMonitoringChannel(const sp<InputChannel>& inputChannel) {
    { // acquire lock
         AutoMutex _l(mDispatchLock);

         for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
             if (mMonitoringChannels[i] == inputChannel) {
                 mMonitoringChannels.removeAt(i);
                 break;
             }
         }
    } // release lock
}

void NativeInputManager::addMonitoringTargetsLd(Vector<InputTarget>& outTargets) {
    for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
        outTargets.push();

        InputTarget& target = outTargets.editTop();
        target.inputChannel = mMonitoringChannels[i];
        target.flags = 0;
        target.timeout = -1;
        target.xOffset = 0;
        target.yOffset = 0;
    }
}

static void dumpMotionRange(String8& dump,
        const char* name, const InputDeviceInfo::MotionRange* range) {
    if (range) {
        dump.appendFormat("      %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n",
                name, range->min, range->max, range->flat, range->fuzz);
    }
}

#define DUMP_MOTION_RANGE(range) \
    dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range));

void NativeInputManager::dumpDeviceInfo(String8& dump) {
    Vector<int32_t> deviceIds;
    mInputManager->getInputDeviceIds(deviceIds);

    InputDeviceInfo deviceInfo;
    for (size_t i = 0; i < deviceIds.size(); i++) {
        int32_t deviceId = deviceIds[i];

        status_t result = mInputManager->getInputDeviceInfo(deviceId, & deviceInfo);
        if (result == NAME_NOT_FOUND) {
            continue;
        } else if (result != OK) {
            dump.appendFormat("  ** Unexpected error %d getting information about input devices.\n",
                    result);
            continue;
        }

        dump.appendFormat("  Device %d: '%s'\n",
                deviceInfo.getId(), deviceInfo.getName().string());
        dump.appendFormat("    sources = 0x%08x\n",
                deviceInfo.getSources());
        dump.appendFormat("    keyboardType = %d\n",
                deviceInfo.getKeyboardType());

        dump.append("    motion ranges:\n");
        DUMP_MOTION_RANGE(X);
        DUMP_MOTION_RANGE(Y);
        DUMP_MOTION_RANGE(PRESSURE);
        DUMP_MOTION_RANGE(SIZE);
        DUMP_MOTION_RANGE(TOUCH_MAJOR);
        DUMP_MOTION_RANGE(TOUCH_MINOR);
        DUMP_MOTION_RANGE(TOOL_MAJOR);
        DUMP_MOTION_RANGE(TOOL_MINOR);
        DUMP_MOTION_RANGE(ORIENTATION);
    }
}

#undef DUMP_MOTION_RANGE

void NativeInputManager::logDispatchStateLd() {
    String8 dump;
    dumpDispatchStateLd(dump);
    LOGD("%s", dump.string());
}

void NativeInputManager::dumpDispatchStateLd(String8& dump) {
    dump.appendFormat("  dispatchEnabled: %d\n", mDispatchEnabled);
    dump.appendFormat("  dispatchFrozen: %d\n", mDispatchFrozen);
    dump.appendFormat("  windowsReady: %d\n", mWindowsReady);

    if (mFocusedApplication) {
        dump.appendFormat("  focusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
                mFocusedApplication->name.string(),
                mFocusedApplication->dispatchingTimeout / 1000000.0);
    } else {
        dump.append("  focusedApplication: <null>\n");
    }
    dump.appendFormat("  focusedWindow: '%s'\n",
            mFocusedWindow != NULL ? mFocusedWindow->inputChannel->getName().string() : "<null>");
    dump.appendFormat("  touchedWindow: '%s', touchDown=%d\n",
            mTouchedWindow != NULL ? mTouchedWindow->inputChannel->getName().string() : "<null>",
            mTouchDown);
    for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) {
        dump.appendFormat("  touchedWallpaperWindows[%d]: '%s'\n",
                i, mTouchedWallpaperWindows[i]->inputChannel->getName().string());
    }
    for (size_t i = 0; i < mWindows.size(); i++) {
        dump.appendFormat("  windows[%d]: '%s', paused=%d, hasFocus=%d, hasWallpaper=%d, "
                "visible=%d, flags=0x%08x, type=0x%08x, "
                "frame=[%d,%d][%d,%d], "
                "visibleFrame=[%d,%d][%d,%d], "
                "touchableArea=[%d,%d][%d,%d], "
                "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
                i, mWindows[i].inputChannel->getName().string(),
                mWindows[i].paused, mWindows[i].hasFocus, mWindows[i].hasWallpaper,
                mWindows[i].visible, mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType,
                mWindows[i].frameLeft, mWindows[i].frameTop,
                mWindows[i].frameRight, mWindows[i].frameBottom,
                mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop,
                mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom,
                mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop,
                mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom,
                mWindows[i].ownerPid, mWindows[i].ownerUid,
                mWindows[i].dispatchingTimeout / 1000000.0);
    }

    for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
        dump.appendFormat("  monitoringChannel[%d]: '%s'\n",
                i, mMonitoringChannels[i]->getName().string());
    }
}

// ----------------------------------------------------------------------------

bool NativeInputManager::InputWindow::visibleFrameIntersects(const InputWindow* other) const {
    return visibleFrameRight > other->visibleFrameLeft
        && visibleFrameLeft < other->visibleFrameRight
        && visibleFrameBottom > other->visibleFrameTop
        && visibleFrameTop < other->visibleFrameBottom;
}

bool NativeInputManager::InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const {
    return x >= touchableAreaLeft && x <= touchableAreaRight
            && y >= touchableAreaTop && y <= touchableAreaBottom;
}

// ----------------------------------------------------------------------------

NativeInputManager::ANRTimer::ANRTimer() :
        mBudget(APPLICATION), mStartTime(now()), mFrozen(false), mPausedWindow(NULL) {
}

void NativeInputManager::ANRTimer::dispatchFrozenBySystem() {
    mFrozen = true;
}

void NativeInputManager::ANRTimer::dispatchPausedByApplication(InputWindow* pausedWindow) {
    mPausedWindow = pausedWindow;
}

bool NativeInputManager::ANRTimer::waitForDispatchStateChangeLd(NativeInputManager* inputManager) {
    nsecs_t currentTime = now();

    Budget newBudget;
    nsecs_t dispatchingTimeout;
    sp<InputChannel> pausedChannel = NULL;
    jobject tokenObj = NULL;
    if (mFrozen) {
        newBudget = SYSTEM;
        dispatchingTimeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
        mFrozen = false;
    } else if (mPausedWindow) {
        newBudget = APPLICATION;
        dispatchingTimeout = mPausedWindow->dispatchingTimeout;
        pausedChannel = mPausedWindow->inputChannel;
        mPausedWindow = NULL;
    } else if (inputManager->mFocusedApplication) {
        newBudget = APPLICATION;
        dispatchingTimeout = inputManager->mFocusedApplication->dispatchingTimeout;
        tokenObj = jniEnv()->NewLocalRef(inputManager->mFocusedApplication->tokenObjWeak);
    } else {
        newBudget = APPLICATION;
        dispatchingTimeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
    }

    if (mBudget != newBudget) {
        mBudget = newBudget;
        mStartTime = currentTime;
    }

    bool result = false;
    nsecs_t timeoutRemaining = mStartTime + dispatchingTimeout - currentTime;
    if (timeoutRemaining > 0
            && inputManager->mDispatchStateChanged.waitRelative(inputManager->mDispatchLock,
                    timeoutRemaining) == OK) {
        result = true;
    } else {
        if (pausedChannel != NULL || tokenObj != NULL) {
            bool resumed;
            nsecs_t newTimeout = 0;

            inputManager->mDispatchLock.unlock(); // release lock
            if (pausedChannel != NULL) {
                resumed = inputManager->notifyInputChannelANR(pausedChannel, /*out*/ newTimeout);
            } else {
                resumed = inputManager->notifyANR(tokenObj, /*out*/ newTimeout);
            }
            inputManager->mDispatchLock.lock(); // re-acquire lock

            if (resumed) {
                mStartTime = now() - dispatchingTimeout + newTimeout;
                result = true;
            }
        }
    }

    if (tokenObj) {
        jniEnv()->DeleteLocalRef(tokenObj);
    }

    return result;
}

nsecs_t NativeInputManager::ANRTimer::getTimeSpentWaitingForApplication() const {
    return mBudget == APPLICATION ? now() - mStartTime : 0;
}

// ----------------------------------------------------------------------------

static sp<NativeInputManager> gNativeInputManager;

static bool checkInputManagerUnitialized(JNIEnv* env) {
    if (gNativeInputManager == NULL) {
        LOGE("Input manager not initialized.");
        jniThrowRuntimeException(env, "Input manager not initialized.");
        return true;
    }
    return false;
}

static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
        jobject callbacks) {
    if (gNativeInputManager == NULL) {
        gNativeInputManager = new NativeInputManager(callbacks);
    } else {
        LOGE("Input manager already initialized.");
        jniThrowRuntimeException(env, "Input manager already initialized.");
    }
}

static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    status_t result = gNativeInputManager->getInputManager()->start();
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}

static void android_server_InputManager_nativeSetDisplaySize(JNIEnv* env, jclass clazz,
        jint displayId, jint width, jint height) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    // 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
    gNativeInputManager->setDisplaySize(displayId, width, height);
}

static void android_server_InputManager_nativeSetDisplayOrientation(JNIEnv* env, jclass clazz,
        jint displayId, jint orientation) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    gNativeInputManager->setDisplayOrientation(displayId, orientation);
}

static jint android_server_InputManager_nativeGetScanCodeState(JNIEnv* env, jclass clazz,
        jint deviceId, jint sourceMask, jint scanCode) {
    if (checkInputManagerUnitialized(env)) {
        return AKEY_STATE_UNKNOWN;
    }

    return gNativeInputManager->getInputManager()->getScanCodeState(
            deviceId, uint32_t(sourceMask), scanCode);
}

static jint android_server_InputManager_nativeGetKeyCodeState(JNIEnv* env, jclass clazz,
        jint deviceId, jint sourceMask, jint keyCode) {
    if (checkInputManagerUnitialized(env)) {
        return AKEY_STATE_UNKNOWN;
    }

    return gNativeInputManager->getInputManager()->getKeyCodeState(
            deviceId, uint32_t(sourceMask), keyCode);
}

static jint android_server_InputManager_nativeGetSwitchState(JNIEnv* env, jclass clazz,
        jint deviceId, jint sourceMask, jint sw) {
    if (checkInputManagerUnitialized(env)) {
        return AKEY_STATE_UNKNOWN;
    }

    return gNativeInputManager->getInputManager()->getSwitchState(
            deviceId, uint32_t(sourceMask), sw);
}

static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass clazz,
        jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) {
    if (checkInputManagerUnitialized(env)) {
        return JNI_FALSE;
    }

    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 = gNativeInputManager->getInputManager()->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 android_server_InputManager_handleInputChannelDisposed(JNIEnv* env,
        jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) {
    LOGW("Input channel object '%s' was disposed without first being unregistered with "
            "the input manager!", inputChannel->getName().string());

    if (gNativeInputManager != NULL) {
        gNativeInputManager->unregisterInputChannel(env, inputChannel);
    }
}

static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
        jobject inputChannelObj, jboolean monitor) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    if (inputChannel == NULL) {
        throwInputChannelNotInitialized(env);
        return;
    }


    status_t status = gNativeInputManager->registerInputChannel(
            env, inputChannel, inputChannelObj, monitor);
    if (status) {
        jniThrowRuntimeException(env, "Failed to register input channel.  "
                "Check logs for details.");
        return;
    }

    if (! monitor) {
        android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
                android_server_InputManager_handleInputChannelDisposed, NULL);
    }
}

static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
        jobject inputChannelObj) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    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 = gNativeInputManager->unregisterInputChannel(env, inputChannel);
    if (status) {
        jniThrowRuntimeException(env, "Failed to unregister input channel.  "
                "Check logs for details.");
    }
}

static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jclass clazz,
        jobject inputEventObj, jint injectorPid, jint injectorUid,
        jint syncMode, jint timeoutMillis) {
    if (checkInputManagerUnitialized(env)) {
        return INPUT_EVENT_INJECTION_FAILED;
    }

    if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
        KeyEvent keyEvent;
        android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);

        return gNativeInputManager->getInputManager()->injectInputEvent(& keyEvent,
                injectorPid, injectorUid, syncMode, timeoutMillis);
    } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
        MotionEvent motionEvent;
        android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent);

        return gNativeInputManager->getInputManager()->injectInputEvent(& motionEvent,
                injectorPid, injectorUid, syncMode, timeoutMillis);
    } else {
        jniThrowRuntimeException(env, "Invalid input event type.");
        return INPUT_EVENT_INJECTION_FAILED;
    }
}

static void android_server_InputManager_nativeSetInputWindows(JNIEnv* env, jclass clazz,
        jobjectArray windowObjArray) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    gNativeInputManager->setInputWindows(env, windowObjArray);
}

static void android_server_InputManager_nativeSetFocusedApplication(JNIEnv* env, jclass clazz,
        jobject applicationObj) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    gNativeInputManager->setFocusedApplication(env, applicationObj);
}

static void android_server_InputManager_nativeSetInputDispatchMode(JNIEnv* env,
        jclass clazz, jboolean enabled, jboolean frozen) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    gNativeInputManager->setInputDispatchMode(enabled, frozen);
}

static void android_server_InputManager_nativePreemptInputDispatch(JNIEnv* env,
        jclass clazz) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    gNativeInputManager->preemptInputDispatch();
}

static jobject android_server_InputManager_nativeGetInputDevice(JNIEnv* env,
        jclass clazz, jint deviceId) {
    if (checkInputManagerUnitialized(env)) {
        return NULL;
    }

    InputDeviceInfo deviceInfo;
    status_t status = gNativeInputManager->getInputManager()->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;
    }

    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());

    const KeyedVector<int, InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
    for (size_t i = 0; i < ranges.size(); i++) {
        int rangeType = ranges.keyAt(i);
        const InputDeviceInfo::MotionRange& range = ranges.valueAt(i);
        env->CallVoidMethod(deviceObj, gInputDeviceClassInfo.addMotionRange,
                rangeType, range.min, range.max, range.flat, range.fuzz);
        if (env->ExceptionCheck()) {
            return NULL;
        }
    }

    return deviceObj;
}

static jintArray android_server_InputManager_nativeGetInputDeviceIds(JNIEnv* env,
        jclass clazz) {
    if (checkInputManagerUnitialized(env)) {
        return NULL;
    }

    Vector<int> deviceIds;
    gNativeInputManager->getInputManager()->getInputDeviceIds(deviceIds);

    jintArray deviceIdsObj = env->NewIntArray(deviceIds.size());
    if (! deviceIdsObj) {
        return NULL;
    }

    env->SetIntArrayRegion(deviceIdsObj, 0, deviceIds.size(), deviceIds.array());
    return deviceIdsObj;
}

static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) {
    if (checkInputManagerUnitialized(env)) {
        return NULL;
    }

    String8 dump(gNativeInputManager->dump());
    return env->NewStringUTF(dump.string());
}

// ----------------------------------------------------------------------------

static JNINativeMethod gInputManagerMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "(Lcom/android/server/InputManager$Callbacks;)V",
            (void*) android_server_InputManager_nativeInit },
    { "nativeStart", "()V",
            (void*) android_server_InputManager_nativeStart },
    { "nativeSetDisplaySize", "(III)V",
            (void*) android_server_InputManager_nativeSetDisplaySize },
    { "nativeSetDisplayOrientation", "(II)V",
            (void*) android_server_InputManager_nativeSetDisplayOrientation },
    { "nativeGetScanCodeState", "(III)I",
            (void*) android_server_InputManager_nativeGetScanCodeState },
    { "nativeGetKeyCodeState", "(III)I",
            (void*) android_server_InputManager_nativeGetKeyCodeState },
    { "nativeGetSwitchState", "(III)I",
            (void*) android_server_InputManager_nativeGetSwitchState },
    { "nativeHasKeys", "(II[I[Z)Z",
            (void*) android_server_InputManager_nativeHasKeys },
    { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;Z)V",
            (void*) android_server_InputManager_nativeRegisterInputChannel },
    { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V",
            (void*) android_server_InputManager_nativeUnregisterInputChannel },
    { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIII)I",
            (void*) android_server_InputManager_nativeInjectInputEvent },
    { "nativeSetInputWindows", "([Lcom/android/server/InputWindow;)V",
            (void*) android_server_InputManager_nativeSetInputWindows },
    { "nativeSetFocusedApplication", "(Lcom/android/server/InputApplication;)V",
            (void*) android_server_InputManager_nativeSetFocusedApplication },
    { "nativeSetInputDispatchMode", "(ZZ)V",
            (void*) android_server_InputManager_nativeSetInputDispatchMode },
    { "nativePreemptInputDispatch", "()V",
            (void*) android_server_InputManager_nativePreemptInputDispatch },
    { "nativeGetInputDevice", "(I)Landroid/view/InputDevice;",
            (void*) android_server_InputManager_nativeGetInputDevice },
    { "nativeGetInputDeviceIds", "()[I",
            (void*) android_server_InputManager_nativeGetInputDeviceIds },
    { "nativeDump", "()Ljava/lang/String;",
            (void*) android_server_InputManager_nativeDump },
};

#define FIND_CLASS(var, className) \
        var = env->FindClass(className); \
        LOG_FATAL_IF(! var, "Unable to find class " className); \
        var = jclass(env->NewGlobalRef(var));

#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/InputManager",
            gInputManagerMethods, NELEM(gInputManagerMethods));
    LOG_FATAL_IF(res < 0, "Unable to register native methods.");

    // Callbacks

    FIND_CLASS(gCallbacksClassInfo.clazz, "com/android/server/InputManager$Callbacks");

    GET_METHOD_ID(gCallbacksClassInfo.notifyConfigurationChanged, gCallbacksClassInfo.clazz,
            "notifyConfigurationChanged", "(JIII)V");

    GET_METHOD_ID(gCallbacksClassInfo.notifyLidSwitchChanged, gCallbacksClassInfo.clazz,
            "notifyLidSwitchChanged", "(JZ)V");

    GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelBroken, gCallbacksClassInfo.clazz,
            "notifyInputChannelBroken", "(Landroid/view/InputChannel;)V");

    GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelANR, gCallbacksClassInfo.clazz,
            "notifyInputChannelANR", "(Landroid/view/InputChannel;)J");

    GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelRecoveredFromANR, gCallbacksClassInfo.clazz,
            "notifyInputChannelRecoveredFromANR", "(Landroid/view/InputChannel;)V");

    GET_METHOD_ID(gCallbacksClassInfo.notifyANR, gCallbacksClassInfo.clazz,
            "notifyANR", "(Ljava/lang/Object;)J");

    GET_METHOD_ID(gCallbacksClassInfo.virtualKeyDownFeedback, gCallbacksClassInfo.clazz,
            "virtualKeyDownFeedback", "()V");

    GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, gCallbacksClassInfo.clazz,
            "interceptKeyBeforeQueueing", "(JIZIZ)I");

    GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, gCallbacksClassInfo.clazz,
            "interceptKeyBeforeDispatching", "(Landroid/view/InputChannel;IIIIII)Z");

    GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz,
            "checkInjectEventsPermission", "(II)Z");

    GET_METHOD_ID(gCallbacksClassInfo.notifyAppSwitchComing, gCallbacksClassInfo.clazz,
            "notifyAppSwitchComing", "()V");

    GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, gCallbacksClassInfo.clazz,
            "filterTouchEvents", "()Z");

    GET_METHOD_ID(gCallbacksClassInfo.filterJumpyTouchEvents, gCallbacksClassInfo.clazz,
            "filterJumpyTouchEvents", "()Z");

    GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyDefinitions, gCallbacksClassInfo.clazz,
            "getVirtualKeyDefinitions",
            "(Ljava/lang/String;)[Lcom/android/server/InputManager$VirtualKeyDefinition;");

    GET_METHOD_ID(gCallbacksClassInfo.getInputDeviceCalibration, gCallbacksClassInfo.clazz,
            "getInputDeviceCalibration",
            "(Ljava/lang/String;)Lcom/android/server/InputManager$InputDeviceCalibration;");

    GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz,
            "getExcludedDeviceNames", "()[Ljava/lang/String;");

    GET_METHOD_ID(gCallbacksClassInfo.getMaxEventsPerSecond, gCallbacksClassInfo.clazz,
            "getMaxEventsPerSecond", "()I");

    // VirtualKeyDefinition

    FIND_CLASS(gVirtualKeyDefinitionClassInfo.clazz,
            "com/android/server/InputManager$VirtualKeyDefinition");

    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.scanCode, gVirtualKeyDefinitionClassInfo.clazz,
            "scanCode", "I");

    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerX, gVirtualKeyDefinitionClassInfo.clazz,
            "centerX", "I");

    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerY, gVirtualKeyDefinitionClassInfo.clazz,
            "centerY", "I");

    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.width, gVirtualKeyDefinitionClassInfo.clazz,
            "width", "I");

    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz,
            "height", "I");

    // InputDeviceCalibration

    FIND_CLASS(gInputDeviceCalibrationClassInfo.clazz,
            "com/android/server/InputManager$InputDeviceCalibration");

    GET_FIELD_ID(gInputDeviceCalibrationClassInfo.keys, gInputDeviceCalibrationClassInfo.clazz,
            "keys", "[Ljava/lang/String;");

    GET_FIELD_ID(gInputDeviceCalibrationClassInfo.values, gInputDeviceCalibrationClassInfo.clazz,
            "values", "[Ljava/lang/String;");

    // InputWindow

    FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/InputWindow");

    GET_FIELD_ID(gInputWindowClassInfo.inputChannel, gInputWindowClassInfo.clazz,
            "inputChannel", "Landroid/view/InputChannel;");

    GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, gInputWindowClassInfo.clazz,
            "layoutParamsFlags", "I");

    GET_FIELD_ID(gInputWindowClassInfo.layoutParamsType, gInputWindowClassInfo.clazz,
            "layoutParamsType", "I");

    GET_FIELD_ID(gInputWindowClassInfo.dispatchingTimeoutNanos, gInputWindowClassInfo.clazz,
            "dispatchingTimeoutNanos", "J");

    GET_FIELD_ID(gInputWindowClassInfo.frameLeft, gInputWindowClassInfo.clazz,
            "frameLeft", "I");

    GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz,
            "frameTop", "I");

    GET_FIELD_ID(gInputWindowClassInfo.frameRight, gInputWindowClassInfo.clazz,
            "frameRight", "I");

    GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz,
            "frameBottom", "I");

    GET_FIELD_ID(gInputWindowClassInfo.visibleFrameLeft, gInputWindowClassInfo.clazz,
            "visibleFrameLeft", "I");

    GET_FIELD_ID(gInputWindowClassInfo.visibleFrameTop, gInputWindowClassInfo.clazz,
            "visibleFrameTop", "I");

    GET_FIELD_ID(gInputWindowClassInfo.visibleFrameRight, gInputWindowClassInfo.clazz,
            "visibleFrameRight", "I");

    GET_FIELD_ID(gInputWindowClassInfo.visibleFrameBottom, gInputWindowClassInfo.clazz,
            "visibleFrameBottom", "I");

    GET_FIELD_ID(gInputWindowClassInfo.touchableAreaLeft, gInputWindowClassInfo.clazz,
            "touchableAreaLeft", "I");

    GET_FIELD_ID(gInputWindowClassInfo.touchableAreaTop, gInputWindowClassInfo.clazz,
            "touchableAreaTop", "I");

    GET_FIELD_ID(gInputWindowClassInfo.touchableAreaRight, gInputWindowClassInfo.clazz,
            "touchableAreaRight", "I");

    GET_FIELD_ID(gInputWindowClassInfo.touchableAreaBottom, gInputWindowClassInfo.clazz,
            "touchableAreaBottom", "I");

    GET_FIELD_ID(gInputWindowClassInfo.visible, gInputWindowClassInfo.clazz,
            "visible", "Z");

    GET_FIELD_ID(gInputWindowClassInfo.hasFocus, gInputWindowClassInfo.clazz,
            "hasFocus", "Z");

    GET_FIELD_ID(gInputWindowClassInfo.hasWallpaper, gInputWindowClassInfo.clazz,
            "hasWallpaper", "Z");

    GET_FIELD_ID(gInputWindowClassInfo.paused, gInputWindowClassInfo.clazz,
            "paused", "Z");

    GET_FIELD_ID(gInputWindowClassInfo.ownerPid, gInputWindowClassInfo.clazz,
            "ownerPid", "I");

    GET_FIELD_ID(gInputWindowClassInfo.ownerUid, gInputWindowClassInfo.clazz,
            "ownerUid", "I");

    // InputApplication

    FIND_CLASS(gInputApplicationClassInfo.clazz, "com/android/server/InputApplication");

    GET_FIELD_ID(gInputApplicationClassInfo.name, gInputApplicationClassInfo.clazz,
            "name", "Ljava/lang/String;");

    GET_FIELD_ID(gInputApplicationClassInfo.dispatchingTimeoutNanos,
            gInputApplicationClassInfo.clazz,
            "dispatchingTimeoutNanos", "J");

    GET_FIELD_ID(gInputApplicationClassInfo.token, gInputApplicationClassInfo.clazz,
            "token", "Ljava/lang/Object;");

    // KeyEvent

    FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent");

    // MotionEvent

    FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent");

    // InputDevice

    FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");

    GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
            "<init>", "()V");

    GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
            "addMotionRange", "(IFFFF)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.mMotionRanges, gInputDeviceClassInfo.clazz,
            "mMotionRanges", "[Landroid/view/InputDevice$MotionRange;");

    return 0;
}

} /* namespace android */
