diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index bfd29595b2dc2..17bdff2c96287 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -104,23 +104,23 @@ public interface WindowManagerPolicy { */ public final static String EXTRA_HDMI_PLUGGED_STATE = "state"; - // flags for interceptKeyTq /** - * Pass this event to the user / app. To be returned from {@link #interceptKeyTq}. + * Pass this event to the user / app. To be returned from + * {@link #interceptKeyBeforeQueueing}. */ public final static int ACTION_PASS_TO_USER = 0x00000001; /** * This key event should extend the user activity timeout and turn the lights on. - * To be returned from {@link #interceptKeyTq}. Do not return this and - * {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}. + * To be returned from {@link #interceptKeyBeforeQueueing}. + * Do not return this and {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}. */ public final static int ACTION_POKE_USER_ACTIVITY = 0x00000002; /** * This key event should put the device to sleep (and engage keyguard if necessary) - * To be returned from {@link #interceptKeyTq}. Do not return this and - * {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}. + * To be returned from {@link #interceptKeyBeforeQueueing}. + * Do not return this and {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}. */ public final static int ACTION_GO_TO_SLEEP = 0x00000004; @@ -677,10 +677,12 @@ public interface WindowManagerPolicy { * event will normally go. * @param event The key event. * @param policyFlags The policy flags associated with the key. - * @return Returns true if the policy consumed the event and it should - * not be further dispatched. + * @return 0 if the key should be dispatched immediately, -1 if the key should + * not be dispatched ever, or a positive value indicating the number of + * milliseconds by which the key dispatch should be delayed before trying + * again. */ - public boolean interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags); + public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags); /** * Called from the input dispatcher thread when an application did not handle diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 487063d2907b9..3eb04cb304a1f 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -267,7 +267,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowState mKeyguard = null; KeyguardViewMediator mKeyguardMediator; GlobalActions mGlobalActions; - volatile boolean mPowerKeyHandled; + volatile boolean mPowerKeyHandled; // accessed from input reader and handler thread + boolean mPendingPowerKeyUpCanceled; RecentApplicationsDialog mRecentAppsDialog; Handler mHandler; @@ -403,8 +404,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { private int mLongPressOnHomeBehavior = -1; // Screenshot trigger states - private boolean mVolumeDownTriggered; - private boolean mPowerDownTriggered; + // Time to volume and power must be pressed within this interval of each other. + private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150; + private boolean mVolumeDownKeyTriggered; + private long mVolumeDownKeyTime; + private boolean mVolumeDownKeyConsumedByScreenshotChord; + private boolean mVolumeUpKeyTriggered; + private boolean mPowerKeyTriggered; + private long mPowerKeyTime; ShortcutManager mShortcutManager; PowerManager.WakeLock mBroadcastWakeLock; @@ -552,37 +559,64 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!mPowerKeyHandled) { mHandler.removeCallbacks(mPowerLongPress); return !canceled; - } else { - mPowerKeyHandled = true; - return false; } + return false; + } + + private void cancelPendingPowerKeyAction() { + if (!mPowerKeyHandled) { + mHandler.removeCallbacks(mPowerLongPress); + } + mPendingPowerKeyUpCanceled = true; + } + + private void interceptScreenshotChord() { + if (mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) { + final long now = SystemClock.uptimeMillis(); + if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS + && now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) { + mVolumeDownKeyConsumedByScreenshotChord = true; + cancelPendingPowerKeyAction(); + + mHandler.postDelayed(mScreenshotChordLongPress, + ViewConfiguration.getGlobalActionKeyTimeout()); + } + } + } + + private void cancelPendingScreenshotChordAction() { + mHandler.removeCallbacks(mScreenshotChordLongPress); } private final Runnable mPowerLongPress = new Runnable() { public void run() { - if (!mPowerKeyHandled) { - // The context isn't read - if (mLongPressOnPowerBehavior < 0) { - mLongPressOnPowerBehavior = mContext.getResources().getInteger( - com.android.internal.R.integer.config_longPressOnPowerBehavior); - } - switch (mLongPressOnPowerBehavior) { - case LONG_PRESS_POWER_NOTHING: - break; - case LONG_PRESS_POWER_GLOBAL_ACTIONS: - mPowerKeyHandled = true; - performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); - sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); - showGlobalActionsDialog(); - break; - case LONG_PRESS_POWER_SHUT_OFF: - mPowerKeyHandled = true; - performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); - sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); - ShutdownThread.shutdown(mContext, true); - break; - } + // The context isn't read + if (mLongPressOnPowerBehavior < 0) { + mLongPressOnPowerBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longPressOnPowerBehavior); } + switch (mLongPressOnPowerBehavior) { + case LONG_PRESS_POWER_NOTHING: + break; + case LONG_PRESS_POWER_GLOBAL_ACTIONS: + mPowerKeyHandled = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); + showGlobalActionsDialog(); + break; + case LONG_PRESS_POWER_SHUT_OFF: + mPowerKeyHandled = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); + ShutdownThread.shutdown(mContext, true); + break; + } + } + }; + + private final Runnable mScreenshotChordLongPress = new Runnable() { + public void run() { + takeScreenshot(); } }; @@ -1381,11 +1415,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override - public boolean interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) { + public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) { final boolean keyguardOn = keyguardOn(); final int keyCode = event.getKeyCode(); final int repeatCount = event.getRepeatCount(); final int metaState = event.getMetaState(); + final int flags = event.getFlags(); final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final boolean canceled = event.isCanceled(); @@ -1394,6 +1429,26 @@ public class PhoneWindowManager implements WindowManagerPolicy { + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed); } + // If we think we might have a volume down & power key chord on the way + // but we're not sure, then tell the dispatcher to wait a little while and + // try again later before dispatching. + if ((flags & KeyEvent.FLAG_FALLBACK) == 0) { + if (mVolumeDownKeyTriggered && !mPowerKeyTriggered) { + final long now = SystemClock.uptimeMillis(); + final long timeoutTime = mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS; + if (now < timeoutTime) { + return timeoutTime - now; + } + } + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + && mVolumeDownKeyConsumedByScreenshotChord) { + if (!down) { + mVolumeDownKeyConsumedByScreenshotChord = false; + } + return -1; + } + } + // First we always handle the home key here, so applications // can never break it, although if keyguard is on, we do let // it handle it, because that gives us the correct 5 second @@ -1425,7 +1480,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else { Log.i(TAG, "Ignoring HOME; event canceled."); } - return true; + return -1; } // If a system window has focus, then it doesn't make sense @@ -1436,13 +1491,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (type == WindowManager.LayoutParams.TYPE_KEYGUARD || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) { // the "app" is keyguard, so give it the key - return false; + return 0; } final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length; for (int i=0; iinterceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { + if (currentTime < entry->interceptKeyWakeupTime) { + if (entry->interceptKeyWakeupTime < *nextWakeupTime) { + *nextWakeupTime = entry->interceptKeyWakeupTime; + } + return false; // wait until next wakeup + } + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; + entry->interceptKeyWakeupTime = 0; + } + // Give the policy a chance to intercept the key. if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { @@ -3827,14 +3839,19 @@ void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible( mLock.unlock(); - bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle, + nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle, &event, entry->policyFlags); mLock.lock(); - entry->interceptKeyResult = consumed - ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP - : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; + if (delay < 0) { + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP; + } else if (!delay) { + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; + } else { + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER; + entry->interceptKeyWakeupTime = now() + delay; + } entry->release(); } @@ -4156,7 +4173,8 @@ InputDispatcher::KeyEntry::KeyEntry(nsecs_t eventTime, deviceId(deviceId), source(source), action(action), flags(flags), keyCode(keyCode), scanCode(scanCode), metaState(metaState), repeatCount(repeatCount), downTime(downTime), - syntheticRepeat(false), interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { + syntheticRepeat(false), interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN), + interceptKeyWakeupTime(0) { } InputDispatcher::KeyEntry::~KeyEntry() { @@ -4168,6 +4186,7 @@ void InputDispatcher::KeyEntry::recycle() { dispatchInProgress = false; syntheticRepeat = false; interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; + interceptKeyWakeupTime = 0; } diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h index e78f7bd0a4cd3..8ae5a56ba8376 100644 --- a/services/input/InputDispatcher.h +++ b/services/input/InputDispatcher.h @@ -242,7 +242,7 @@ public: virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0; /* Allows the policy a chance to intercept a key before dispatching. */ - virtual bool interceptKeyBeforeDispatching(const sp& inputWindowHandle, + virtual nsecs_t interceptKeyBeforeDispatching(const sp& inputWindowHandle, const KeyEvent* keyEvent, uint32_t policyFlags) = 0; /* Allows the policy a chance to perform default processing for an unhandled key. @@ -481,8 +481,10 @@ private: INTERCEPT_KEY_RESULT_UNKNOWN, INTERCEPT_KEY_RESULT_SKIP, INTERCEPT_KEY_RESULT_CONTINUE, + INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER, }; InterceptKeyResult interceptKeyResult; // set based on the interception result + nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER KeyEntry(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp index 8dfb44b376a08..961566fe81573 100644 --- a/services/input/tests/InputDispatcher_test.cpp +++ b/services/input/tests/InputDispatcher_test.cpp @@ -75,9 +75,9 @@ private: virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) { } - virtual bool interceptKeyBeforeDispatching(const sp& inputWindowHandle, + virtual nsecs_t interceptKeyBeforeDispatching(const sp& inputWindowHandle, const KeyEvent* keyEvent, uint32_t policyFlags) { - return false; + return 0; } virtual bool dispatchUnhandledKey(const sp& inputWindowHandle, diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java index 60333a3bf3f31..df7e0e18b7f52 100644 --- a/services/java/com/android/server/wm/InputManager.java +++ b/services/java/com/android/server/wm/InputManager.java @@ -575,7 +575,7 @@ public class InputManager implements Watchdog.Monitor { } @SuppressWarnings("unused") - public boolean interceptKeyBeforeDispatching(InputWindowHandle focus, + public long interceptKeyBeforeDispatching(InputWindowHandle focus, KeyEvent event, int policyFlags) { return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching( focus, event, policyFlags); diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java index 9a559e004412c..fb74d27642807 100644 --- a/services/java/com/android/server/wm/InputMonitor.java +++ b/services/java/com/android/server/wm/InputMonitor.java @@ -288,7 +288,7 @@ final class InputMonitor { /* Provides an opportunity for the window manager policy to process a key before * ordinary dispatch. */ - public boolean interceptKeyBeforeDispatching( + public long interceptKeyBeforeDispatching( InputWindowHandle focus, KeyEvent event, int policyFlags) { WindowState windowState = focus != null ? (WindowState) focus.windowState : null; return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags); diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index f9763016cebab..7e9fba8919468 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -149,6 +149,12 @@ static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t styl } } +enum { + WM_ACTION_PASS_TO_USER = 1, + WM_ACTION_POKE_USER_ACTIVITY = 2, + WM_ACTION_GO_TO_SLEEP = 4, +}; + // --- NativeInputManager --- @@ -199,7 +205,8 @@ public: virtual bool isKeyRepeatEnabled(); virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags); virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags); - virtual bool interceptKeyBeforeDispatching(const sp& inputWindowHandle, + virtual nsecs_t interceptKeyBeforeDispatching( + const sp& inputWindowHandle, const KeyEvent* keyEvent, uint32_t policyFlags); virtual bool dispatchUnhandledKey(const sp& inputWindowHandle, const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent); @@ -819,12 +826,6 @@ void NativeInputManager::interceptMotionBeforeQueueing(nsecs_t when, uint32_t& p void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags) { - enum { - WM_ACTION_PASS_TO_USER = 1, - WM_ACTION_POKE_USER_ACTIVITY = 2, - WM_ACTION_GO_TO_SLEEP = 4, - }; - if (wmActions & WM_ACTION_GO_TO_SLEEP) { #if DEBUG_INPUT_DISPATCHER_POLICY LOGD("handleInterceptActions: Going to sleep."); @@ -848,14 +849,14 @@ void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when, } } -bool NativeInputManager::interceptKeyBeforeDispatching( +nsecs_t NativeInputManager::interceptKeyBeforeDispatching( const sp& inputWindowHandle, const KeyEvent* keyEvent, uint32_t policyFlags) { // Policy: // - Ignore untrusted events and pass them along. // - Filter normal events and trusted injected events through the window manager policy to // handle the HOME key and the like. - bool result = false; + nsecs_t result = 0; if (policyFlags & POLICY_FLAG_TRUSTED) { JNIEnv* env = jniEnv(); @@ -863,13 +864,19 @@ bool NativeInputManager::interceptKeyBeforeDispatching( jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle); jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent); if (keyEventObj) { - jboolean consumed = env->CallBooleanMethod(mCallbacksObj, + jlong delayMillis = env->CallLongMethod(mCallbacksObj, gCallbacksClassInfo.interceptKeyBeforeDispatching, inputWindowHandleObj, keyEventObj, policyFlags); bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching"); android_view_KeyEvent_recycle(env, keyEventObj); env->DeleteLocalRef(keyEventObj); - result = consumed && !error; + if (!error) { + if (delayMillis < 0) { + result = -1; + } else if (delayMillis > 0) { + result = milliseconds_to_nanoseconds(delayMillis); + } + } } else { LOGE("Failed to obtain key event object for interceptKeyBeforeDispatching."); } @@ -1433,7 +1440,7 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, clazz, "interceptKeyBeforeDispatching", - "(Lcom/android/server/wm/InputWindowHandle;Landroid/view/KeyEvent;I)Z"); + "(Lcom/android/server/wm/InputWindowHandle;Landroid/view/KeyEvent;I)J"); GET_METHOD_ID(gCallbacksClassInfo.dispatchUnhandledKey, clazz, "dispatchUnhandledKey",