diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 5da55b164f542..f4c642fafedee 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5344,6 +5344,13 @@ public final class Settings { */ public static final String LONG_PRESS_TIMEOUT = "long_press_timeout"; + /** + * The duration in milliseconds between the first tap's up event and the second tap's + * down event for an interaction to be considered part of the same multi-press. + * @hide + */ + public static final String MULTI_PRESS_TIMEOUT = "multi_press_timeout"; + /** * List of the enabled print services. * diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 4d584a3df4fb8..9a73d0b1bfaa6 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -63,6 +63,12 @@ public class ViewConfiguration { */ private static final int DEFAULT_LONG_PRESS_TIMEOUT = 500; + /** + * Defines the default duration in milliseconds between the first tap's up event and the second + * tap's down event for an interaction to be considered part of the same multi-press. + */ + private static final int DEFAULT_MULTI_PRESS_TIMEOUT = 300; + /** * Defines the time between successive key repeats in milliseconds. */ @@ -440,6 +446,16 @@ public class ViewConfiguration { DEFAULT_LONG_PRESS_TIMEOUT); } + /** + * @return the duration in milliseconds between the first tap's up event and the second tap's + * down event for an interaction to be considered part of the same multi-press. + * @hide + */ + public static int getMultiPressTimeout() { + return AppGlobals.getIntCoreSetting(Settings.Secure.MULTI_PRESS_TIMEOUT, + DEFAULT_MULTI_PRESS_TIMEOUT); + } + /** * @return the time before the first key repeat in milliseconds. */ diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 018072b0ea943..18a6a839f9002 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -796,6 +796,12 @@ --> 0 + + 0 + 400 + + 300 + false diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 0f7fe6fc5124f..0f3a7fbe9daa4 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2102,7 +2102,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 130; + private static final int SETTINGS_VERSION = 131; private final int mUserId; @@ -2410,6 +2410,22 @@ public class SettingsProvider extends ContentProvider { currentVersion = 130; } + if (currentVersion == 130) { + // Initialize new multi-press timeout to default value + final SettingsState systemSecureSettings = getSecureSettingsLocked(userId); + final String oldValue = systemSecureSettings.getSettingLocked( + Settings.Secure.MULTI_PRESS_TIMEOUT).getValue(); + if (TextUtils.equals(null, oldValue)) { + systemSecureSettings.insertSettingLocked( + Settings.Secure.MULTI_PRESS_TIMEOUT, + String.valueOf(getContext().getResources().getInteger( + R.integer.def_multi_press_timeout_millis)), + SettingsState.SYSTEM_PACKAGE_NAME); + } + + currentVersion = 131; + } + if (currentVersion != newVersion) { Slog.w("SettingsProvider", "warning: upgrading settings database to version " + newVersion + " left it at " diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 9dd07a9a9f90a..73a17c613c4d0 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -42,6 +42,7 @@ final class CoreSettingsObserver extends ContentObserver { String, Class>(); static { sSecureSettingToTypeMap.put(Settings.Secure.LONG_PRESS_TIMEOUT, int.class); + sSecureSettingToTypeMap.put(Settings.Secure.MULTI_PRESS_TIMEOUT, int.class); // add other secure settings here... sSystemSettingToTypeMap.put(Settings.System.TIME_12_24, String.class); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index e502764f33b29..dcee44f991860 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -200,6 +200,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int MULTI_PRESS_POWER_THEATER_MODE = 1; static final int MULTI_PRESS_POWER_BRIGHTNESS_BOOST = 2; + // Number of presses needed before we induce panic press behavior on the back button + static final int PANIC_PRESS_BACK_COUNT = 4; + static final int PANIC_PRESS_BACK_NOTHING = 0; + static final int PANIC_PRESS_BACK_HOME = 1; + // These need to match the documentation/constant in // core/res/res/values/config.xml static final int LONG_PRESS_HOME_NOTHING = 0; @@ -406,6 +411,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { volatile boolean mBackKeyHandled; volatile boolean mBeganFromNonInteractive; volatile int mPowerKeyPressCounter; + volatile int mBackKeyPressCounter; volatile boolean mEndCallKeyHandled; volatile boolean mCameraGestureTriggeredDuringGoingToSleep; volatile boolean mGoingToSleep; @@ -464,6 +470,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mDoublePressOnPowerBehavior; int mTriplePressOnPowerBehavior; int mLongPressOnBackBehavior; + int mPanicPressOnBackBehavior; int mShortPressOnSleepBehavior; int mShortPressWindowBehavior; boolean mAwake; @@ -726,6 +733,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_SHOW_TV_PICTURE_IN_PICTURE_MENU = 17; private static final int MSG_BACK_LONG_PRESS = 18; private static final int MSG_DISPOSE_INPUT_CONSUMER = 19; + private static final int MSG_BACK_DELAYED_PRESS = 20; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1; @@ -792,10 +800,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case MSG_BACK_LONG_PRESS: backLongPress(); + finishBackKeyPress(); break; case MSG_DISPOSE_INPUT_CONSUMER: disposeInputConsumer((InputConsumer) msg.obj); break; + case MSG_BACK_DELAYED_PRESS: + backMultiPressAction((Long) msg.obj, msg.arg1); + finishBackKeyPress(); + break; } } } @@ -1010,6 +1023,52 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private void interceptBackKeyDown() { + // Reset back key state for long press + mBackKeyHandled = false; + + // Cancel multi-press detection timeout. + if (hasPanicPressOnBackBehavior()) { + if (mBackKeyPressCounter != 0 + && mBackKeyPressCounter < PANIC_PRESS_BACK_COUNT) { + mHandler.removeMessages(MSG_BACK_DELAYED_PRESS); + } + } + + if (hasLongPressOnBackBehavior()) { + Message msg = mHandler.obtainMessage(MSG_BACK_LONG_PRESS); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, + ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + } + } + + // returns true if the key was handled and should not be passed to the user + private boolean interceptBackKeyUp(KeyEvent event) { + // Cache handled state + boolean handled = mBackKeyHandled; + + if (hasPanicPressOnBackBehavior()) { + // Check for back key panic press + ++mBackKeyPressCounter; + + final long eventTime = event.getDownTime(); + + if (mBackKeyPressCounter <= PANIC_PRESS_BACK_COUNT) { + // This could be a multi-press. Wait a little bit longer to confirm. + Message msg = mHandler.obtainMessage(MSG_BACK_DELAYED_PRESS, + mBackKeyPressCounter, 0, eventTime); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout()); + } + } + + // Reset back long press state + cancelPendingBackKeyAction(); + + return handled; + } + private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { // Hold a wake lock until the power key is released. if (!mPowerKeyWakeLock.isHeld()) { @@ -1140,6 +1199,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private void finishBackKeyPress() { + mBackKeyPressCounter = 0; + } + private void cancelPendingPowerKeyAction() { if (!mPowerKeyHandled) { mPowerKeyHandled = true; @@ -1154,6 +1217,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private void backMultiPressAction(long eventTime, int count) { + if (count >= PANIC_PRESS_BACK_COUNT) { + switch (mPanicPressOnBackBehavior) { + case PANIC_PRESS_BACK_NOTHING: + break; + case PANIC_PRESS_BACK_HOME: + launchHomeFromHotKey(); + break; + } + } + } + private void powerPress(long eventTime, boolean interactive, int count) { if (mScreenOnEarly && !mScreenOnFully) { Slog.i(TAG, "Suppressed redundant power key press while " @@ -1312,6 +1387,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING; } + private boolean hasPanicPressOnBackBehavior() { + return mPanicPressOnBackBehavior != PANIC_PRESS_BACK_NOTHING; + } + private void interceptScreenshotChord() { if (mScreenshotChordEnabled && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered @@ -1639,6 +1718,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mLongPressOnBackBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_longPressOnBackBehavior); + mPanicPressOnBackBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_backPanicBehavior); mShortPressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_shortPressOnPowerBehavior); @@ -5607,20 +5688,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { switch (keyCode) { case KeyEvent.KEYCODE_BACK: { if (down) { - mBackKeyHandled = false; - if (hasLongPressOnBackBehavior()) { - Message msg = mHandler.obtainMessage(MSG_BACK_LONG_PRESS); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, - ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); - } + interceptBackKeyDown(); } else { - boolean handled = mBackKeyHandled; + boolean handled = interceptBackKeyUp(event); - // Reset back key state - cancelPendingBackKeyAction(); - - // Don't pass back press to app if we've already handled it + // Don't pass back press to app if we've already handled it via long press if (handled) { result &= ~ACTION_PASS_TO_USER; }