| /* |
| * 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 getExcludedDeviceNames; |
| } gCallbacksClassInfo; |
| |
| static struct { |
| jclass clazz; |
| |
| jfieldID scanCode; |
| jfieldID centerX; |
| jfieldID centerY; |
| jfieldID width; |
| jfieldID height; |
| } gVirtualKeyDefinitionClassInfo; |
| |
| static struct { |
| jclass clazz; |
| |
| jfieldID inputChannel; |
| jfieldID layoutParamsFlags; |
| jfieldID layoutParamsType; |
| jfieldID dispatchingTimeoutNanos; |
| jfieldID frameLeft; |
| jfieldID frameTop; |
| 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 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; } |
| |
| 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); |
| 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 interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown, |
| bool rolled); |
| virtual int32_t interceptTouch(nsecs_t when); |
| virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue); |
| virtual bool filterTouchEvents(); |
| virtual bool filterJumpyTouchEvents(); |
| virtual void getVirtualKeyDefinitions(const String8& deviceName, |
| Vector<InputReaderPolicyInterface::VirtualKeyDefinition>& outVirtualKeyDefinitions); |
| 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); |
| |
| private: |
| struct InputWindow { |
| sp<InputChannel> inputChannel; |
| int32_t layoutParamsFlags; |
| int32_t layoutParamsType; |
| nsecs_t dispatchingTimeout; |
| int32_t frameLeft; |
| int32_t frameTop; |
| 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; |
| |
| inline bool touchableAreaContainsPoint(int32_t x, int32_t y) { |
| return x >= touchableAreaLeft && x <= touchableAreaRight |
| && y >= touchableAreaTop && y <= touchableAreaBottom; |
| } |
| }; |
| |
| 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 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 receive fd. |
| Mutex mInputChannelRegistryLock; |
| KeyedVector<int, jweak> mInputChannelObjWeakByReceiveFd; |
| |
| 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; |
| |
| // Focus tracking for keys, trackball, etc. |
| InputWindow* mFocusedWindow; |
| |
| // Focus tracking for touch. |
| bool mTouchDown; |
| InputWindow* mTouchedWindow; // primary target for current down |
| Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets |
| |
| Vector<InputWindow*> mTempTouchedOutsideWindows; // temporary outside touch targets |
| Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets |
| |
| // Focused application. |
| InputApplication* mFocusedApplication; |
| InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication |
| |
| void dumpDispatchStateLd(); |
| |
| 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); |
| |
| void releaseTouchedWindowLd(); |
| |
| int32_t waitForTrackballEventTargets(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); |
| |
| 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), |
| 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); |
| } |
| |
| 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) { |
| 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 = mInputChannelObjWeakByReceiveFd.indexOfKey( |
| inputChannel->getReceivePipeFd()); |
| if (index >= 0) { |
| LOGE("Input channel object '%s' has already been registered", |
| inputChannel->getName().string()); |
| status = INVALID_OPERATION; |
| goto DeleteWeakRef; |
| } |
| |
| mInputChannelObjWeakByReceiveFd.add(inputChannel->getReceivePipeFd(), |
| inputChannelObjWeak); |
| } |
| |
| status = mInputManager->registerInputChannel(inputChannel); |
| if (! status) { |
| return OK; |
| } |
| |
| { |
| AutoMutex _l(mInputChannelRegistryLock); |
| mInputChannelObjWeakByReceiveFd.removeItem(inputChannel->getReceivePipeFd()); |
| } |
| |
| DeleteWeakRef: |
| env->DeleteWeakGlobalRef(inputChannelObjWeak); |
| return status; |
| } |
| |
| status_t NativeInputManager::unregisterInputChannel(JNIEnv* env, |
| const sp<InputChannel>& inputChannel) { |
| jweak inputChannelObjWeak; |
| { |
| AutoMutex _l(mInputChannelRegistryLock); |
| |
| ssize_t index = mInputChannelObjWeakByReceiveFd.indexOfKey( |
| inputChannel->getReceivePipeFd()); |
| if (index < 0) { |
| LOGE("Input channel object '%s' is not currently registered", |
| inputChannel->getName().string()); |
| return INVALID_OPERATION; |
| } |
| |
| inputChannelObjWeak = mInputChannelObjWeakByReceiveFd.valueAt(index); |
| mInputChannelObjWeakByReceiveFd.removeItemsAt(index); |
| } |
| |
| env->DeleteWeakGlobalRef(inputChannelObjWeak); |
| |
| return mInputManager->unregisterInputChannel(inputChannel); |
| } |
| |
| jobject NativeInputManager::getInputChannelObjLocal(JNIEnv* env, |
| const sp<InputChannel>& inputChannel) { |
| { |
| AutoMutex _l(mInputChannelRegistryLock); |
| |
| ssize_t index = mInputChannelObjWeakByReceiveFd.indexOfKey( |
| inputChannel->getReceivePipeFd()); |
| if (index < 0) { |
| return NULL; |
| } |
| |
| jweak inputChannelObjWeak = mInputChannelObjWeakByReceiveFd.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) { |
| *width = mDisplayWidth; |
| *height = mDisplayHeight; |
| *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. |
| actions |= InputReaderPolicyInterface::ACTION_WOKE_HERE; |
| } |
| |
| if (! isScreenBright) { |
| // Key presses and releases brighten the screen if dimmed. |
| actions |= InputReaderPolicyInterface::ACTION_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::interceptTouch(nsecs_t when) { |
| #if DEBUG_INPUT_READER_POLICY |
| LOGD("interceptTouch - when=%lld", when); |
| #endif |
| |
| int32_t actions = InputReaderPolicyInterface::ACTION_NONE; |
| if (isScreenOn()) { |
| // Only dispatch touch events when the device is awake. |
| // Do not wake the device. |
| actions |= InputReaderPolicyInterface::ACTION_DISPATCH; |
| |
| if (! isScreenBright()) { |
| // Brighten the screen if dimmed. |
| actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE; |
| } |
| } |
| |
| return actions; |
| } |
| |
| int32_t NativeInputManager::interceptTrackball(nsecs_t when, |
| bool buttonChanged, bool buttonDown, bool rolled) { |
| #if DEBUG_INPUT_READER_POLICY |
| LOGD("interceptTrackball - when=%lld, buttonChanged=%d, buttonDown=%d, rolled=%d", |
| when, buttonChanged, buttonDown, rolled); |
| #endif |
| |
| int32_t actions = InputReaderPolicyInterface::ACTION_NONE; |
| if (isScreenOn()) { |
| // Only dispatch trackball events when the device is awake. |
| // Do not wake the device. |
| actions |= InputReaderPolicyInterface::ACTION_DISPATCH; |
| |
| if (! isScreenBright()) { |
| // Brighten the screen if dimmed. |
| actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE; |
| } |
| } |
| |
| return actions; |
| } |
| |
| int32_t NativeInputManager::interceptSwitch(nsecs_t when, int32_t switchCode, |
| int32_t switchValue) { |
| #if DEBUG_INPUT_READER_POLICY |
| LOGD("interceptSwitch - when=%lld, switchCode=%d, switchValue=%d", |
| when, switchCode, switchValue); |
| #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<InputReaderPolicyInterface::VirtualKeyDefinition>& outVirtualKeyDefinitions) { |
| 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::getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) { |
| 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); |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| 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(); |
| } |
| |
| 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(); |
| |
| mDispatchStateChanged.broadcast(); |
| |
| #if DEBUG_FOCUS |
| dumpDispatchStateLd(); |
| #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 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.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 |
| dumpDispatchStateLd(); |
| #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 |
| dumpDispatchStateLd(); |
| #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); |
| dumpDispatchStateLd(); |
| #endif |
| return injectionResult; |
| } |
| |
| int32_t NativeInputManager::waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags, |
| int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets, |
| InputWindow*& outTouchedWindow) { |
| nsecs_t startTime = now(); |
| |
| int32_t injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; |
| int32_t action = motionEvent->getAction(); |
| |
| // 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; |
| 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; // 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 == MOTION_EVENT_ACTION_DOWN) { |
| InputWindow* newTouchedWindow = NULL; |
| mTempTouchedOutsideWindows.clear(); |
| |
| int32_t x = int32_t(motionEvent->getX(0)); |
| int32_t y = int32_t(motionEvent->getY(0)); |
| InputWindow* topErrorWindow = NULL; |
| |
| // 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; |
| } |
| break; // found touched window, exit window loop |
| } |
| } |
| |
| if (flags & FLAG_WATCH_OUTSIDE_TOUCH) { |
| mTempTouchedOutsideWindows.push(window); |
| } |
| } |
| } |
| |
| // 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; |
| break; // failed, exit wait loop |
| } |
| |
| // Check permissions. |
| if (! checkInjectionPermission(newTouchedWindow, injectorPid, injectorUid)) { |
| injectionResult = INPUT_EVENT_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; |
| |
| if (newTouchedWindow->hasWallpaper) { |
| mTouchedWallpaperWindows.appendVector(mWallpaperWindows); |
| } |
| break; // done |
| } else { |
| // Check permissions. |
| if (! checkInjectionPermission(mTouchedWindow, injectorPid, injectorUid)) { |
| injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; |
| break; // failed, exit wait loop |
| } |
| |
| // If there is no currently touched window then fail. |
| if (! mTouchedWindow || ! mTouchDown) { |
| LOGI("Dropping event because touched window is no longer valid."); |
| injectionResult = INPUT_EVENT_INJECTION_FAILED; |
| 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! |
| break; // done |
| } |
| } |
| |
| // Output targets. |
| bool havePermission; |
| if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) { |
| // Injection succeeded so the injector must have permission. |
| havePermission = true; |
| |
| size_t numWallpaperWindows = mTouchedWallpaperWindows.size(); |
| for (size_t i = 0; i < numWallpaperWindows; i++) { |
| addTarget(mTouchedWallpaperWindows[i], 0, 0, outTargets); |
| } |
| |
| size_t numOutsideWindows = mTempTouchedOutsideWindows.size(); |
| for (size_t i = 0; i < numOutsideWindows; i++) { |
| addTarget(mTempTouchedOutsideWindows[i], InputTarget::FLAG_OUTSIDE, 0, outTargets); |
| } |
| |
| addTarget(mTouchedWindow, InputTarget::FLAG_SYNC, |
| anrTimer.getTimeSpentWaitingForApplication(), outTargets); |
| outTouchedWindow = mTouchedWindow; |
| } else { |
| if (injectionResult != INPUT_EVENT_INJECTION_PERMISSION_DENIED |
| && checkInjectionPermission(NULL, injectorPid, injectorUid)) { |
| // Injection failed but the injector does have permission to inject events. |
| // While we might not have found a valid target for the injected event, we |
| // still want to update the dispatch state to take it into account. |
| havePermission = true; |
| } else { |
| // Injector does not have permission to inject events. |
| // We make sure to leave the dispatch state unchanged. |
| havePermission = false; |
| } |
| outTouchedWindow = NULL; |
| } |
| mTempTouchedOutsideWindows.clear(); |
| |
| // Update final pieces of touch state now that we know for sure whether the injector |
| // had permission to perform the injection. |
| if (havePermission) { |
| if (action == MOTION_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 == MOTION_EVENT_ACTION_UP) { |
| mTouchDown = false; |
| releaseTouchedWindowLd(); |
| } |
| } |
| |
| #if DEBUG_FOCUS |
| LOGD("waitForTouchedWindow finished: injectionResult=%d", |
| injectionResult); |
| dumpDispatchStateLd(); |
| #endif |
| return injectionResult; |
| } |
| |
| void NativeInputManager::releaseTouchedWindowLd() { |
| mTouchedWindow = NULL; |
| 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; |
| } |
| |
| 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; |
| } |
| } |
| |
| 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 |
| |
| switch (motionEvent->getNature()) { |
| case INPUT_EVENT_NATURE_TRACKBALL: |
| return waitForTrackballEventTargets(motionEvent, policyFlags, injectorPid, injectorUid, |
| outTargets); |
| |
| case INPUT_EVENT_NATURE_TOUCH: |
| return waitForTouchEventTargets(motionEvent, policyFlags, injectorPid, injectorUid, |
| outTargets); |
| |
| default: |
| assert(false); |
| return INPUT_EVENT_INJECTION_FAILED; |
| } |
| } |
| |
| int32_t NativeInputManager::waitForTrackballEventTargets(MotionEvent* motionEvent, |
| uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, |
| Vector<InputTarget>& outTargets) { |
| #if DEBUG_INPUT_DISPATCHER_POLICY |
| LOGD("waitForTrackballEventTargets - 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 |
| |
| 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; |
| } // release lock |
| |
| int32_t eventType; |
| switch (motionEvent->getAction()) { |
| case MOTION_EVENT_ACTION_DOWN: |
| eventType = POWER_MANAGER_TOUCH_EVENT; |
| break; |
| case MOTION_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::dumpDispatchStateLd() { |
| #if DEBUG_FOCUS |
| LOGD(" dispatcherState: dispatchEnabled=%d, dispatchFrozen=%d, windowsReady=%d", |
| mDispatchEnabled, mDispatchFrozen, mWindowsReady); |
| if (mFocusedApplication) { |
| LOGD(" focusedApplication: name='%s', dispatchingTimeout=%0.3fms", |
| mFocusedApplication->name.string(), |
| mFocusedApplication->dispatchingTimeout / 1000000.0); |
| } else { |
| LOGD(" focusedApplication: <null>"); |
| } |
| LOGD(" focusedWindow: '%s'", |
| mFocusedWindow != NULL ? mFocusedWindow->inputChannel->getName().string() : "<null>"); |
| LOGD(" touchedWindow: '%s', touchDown=%d", |
| mTouchedWindow != NULL ? mTouchedWindow->inputChannel->getName().string() : "<null>", |
| mTouchDown); |
| for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) { |
| LOGD(" touchedWallpaperWindows[%d]: '%s'", |
| i, mTouchedWallpaperWindows[i]->inputChannel->getName().string()); |
| } |
| for (size_t i = 0; i < mWindows.size(); i++) { |
| LOGD(" windows[%d]: '%s', paused=%d, hasFocus=%d, hasWallpaper=%d, visible=%d, " |
| "flags=0x%08x, type=0x%08x, " |
| "frame=[%d,%d], touchableArea=[%d,%d][%d,%d], " |
| "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms", |
| 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].touchableAreaLeft, mWindows[i].touchableAreaTop, |
| mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom, |
| mWindows[i].ownerPid, mWindows[i].ownerUid, |
| mWindows[i].dispatchingTimeout / 1000000.0); |
| } |
| #endif |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| 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 deviceClasses, jint scanCode) { |
| if (checkInputManagerUnitialized(env)) { |
| return KEY_STATE_UNKNOWN; |
| } |
| |
| return gNativeInputManager->getInputManager()->getScanCodeState( |
| deviceId, deviceClasses, scanCode); |
| } |
| |
| static jint android_server_InputManager_nativeGetKeyCodeState(JNIEnv* env, jclass clazz, |
| jint deviceId, jint deviceClasses, jint keyCode) { |
| if (checkInputManagerUnitialized(env)) { |
| return KEY_STATE_UNKNOWN; |
| } |
| |
| return gNativeInputManager->getInputManager()->getKeyCodeState( |
| deviceId, deviceClasses, keyCode); |
| } |
| |
| static jint android_server_InputManager_nativeGetSwitchState(JNIEnv* env, jclass clazz, |
| jint deviceId, jint deviceClasses, jint sw) { |
| if (checkInputManagerUnitialized(env)) { |
| return KEY_STATE_UNKNOWN; |
| } |
| |
| return gNativeInputManager->getInputManager()->getSwitchState(deviceId, deviceClasses, sw); |
| } |
| |
| static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass clazz, |
| 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(outFlags)) { |
| result = gNativeInputManager->getInputManager()->hasKeys(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) { |
| 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); |
| if (status) { |
| jniThrowRuntimeException(env, "Failed to register input channel. " |
| "Check logs for details."); |
| return; |
| } |
| |
| 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_nativeInjectKeyEvent(JNIEnv* env, jclass clazz, |
| jobject keyEventObj, jint nature, jint injectorPid, jint injectorUid, |
| jboolean sync, jint timeoutMillis) { |
| if (checkInputManagerUnitialized(env)) { |
| return INPUT_EVENT_INJECTION_FAILED; |
| } |
| |
| KeyEvent keyEvent; |
| android_view_KeyEvent_toNative(env, keyEventObj, nature, & keyEvent); |
| |
| return gNativeInputManager->getInputManager()->injectInputEvent(& keyEvent, |
| injectorPid, injectorUid, sync, timeoutMillis); |
| } |
| |
| static jint android_server_InputManager_nativeInjectMotionEvent(JNIEnv* env, jclass clazz, |
| jobject motionEventObj, jint nature, jint injectorPid, jint injectorUid, |
| jboolean sync, jint timeoutMillis) { |
| if (checkInputManagerUnitialized(env)) { |
| return INPUT_EVENT_INJECTION_FAILED; |
| } |
| |
| MotionEvent motionEvent; |
| android_view_MotionEvent_toNative(env, motionEventObj, nature, & motionEvent); |
| |
| return gNativeInputManager->getInputManager()->injectInputEvent(& motionEvent, |
| injectorPid, injectorUid, sync, timeoutMillis); |
| } |
| |
| 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 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", "([I[Z)Z", |
| (void*) android_server_InputManager_nativeHasKeys }, |
| { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;)V", |
| (void*) android_server_InputManager_nativeRegisterInputChannel }, |
| { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V", |
| (void*) android_server_InputManager_nativeUnregisterInputChannel }, |
| { "nativeInjectKeyEvent", "(Landroid/view/KeyEvent;IIIZI)I", |
| (void*) android_server_InputManager_nativeInjectKeyEvent }, |
| { "nativeInjectMotionEvent", "(Landroid/view/MotionEvent;IIIZI)I", |
| (void*) android_server_InputManager_nativeInjectMotionEvent }, |
| { "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 } |
| }; |
| |
| #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.getExcludedDeviceNames, gCallbacksClassInfo.clazz, |
| "getExcludedDeviceNames", "()[Ljava/lang/String;"); |
| |
| // 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"); |
| |
| // 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.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;"); |
| |
| return 0; |
| } |
| |
| } /* namespace android */ |