diff options
| author | 2021-12-06 20:22:32 +0000 | |
|---|---|---|
| committer | 2022-01-26 22:53:59 +0000 | |
| commit | 263b03cbbb64bb0afb3be52f667cb83a26b0f2f6 (patch) | |
| tree | b24427ce5f183401aecdc3de750b4ffcfba46f8c | |
| parent | becb83929689559af94bcc309cefb6decd2de4e5 (diff) | |
Add power button cooldown after emergency gesture is triggered.
Bug: 195782998
Test: unit tests, manual test on device
Change-Id: I80dbfd5e297af6e62ecbd6593dd63686c1b7ac1c
Merged-In: I80dbfd5e297af6e62ecbd6593dd63686c1b7ac1c
(cherry picked from commit c22b2a9520ac6a1820df1599d61e98802278f240)
4 files changed, 358 insertions, 0 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 54952fc2dc39..0ac9839f9014 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9195,6 +9195,16 @@ public final class Settings { "emergency_gesture_sound_enabled"; /** + * The power button "cooldown" period in milliseconds after the Emergency gesture is + * triggered, during which single-key actions on the power button are suppressed. Cooldown + * period is disabled if set to zero. + * + * @hide + */ + public static final String EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS = + "emergency_gesture_power_button_cooldown_period_ms"; + + /** * Whether the camera launch gesture to double tap the power button when the screen is off * should be disabled. * diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 9f883960981b..6671073cd698 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -272,6 +272,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.EMERGENCY_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.EMERGENCY_GESTURE_SOUND_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS, + NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR); diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index d04698cb5aeb..db5aa9db3388 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -83,6 +83,20 @@ public class GestureLauncherService extends SystemService { private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 5; /** + * Default value of the power button "cooldown" period after the Emergency gesture is triggered. + * See {@link Settings.Secure#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS} + */ + private static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT = 3000; + + /** + * Maximum value of the power button "cooldown" period after the Emergency gesture is triggered. + * The value read from {@link Settings.Secure#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS} + * is capped at this maximum. + */ + @VisibleForTesting + static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000; + + /** * Number of taps required to launch camera shortcut. */ private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2; @@ -145,7 +159,14 @@ public class GestureLauncherService extends SystemService { */ private boolean mEmergencyGestureEnabled; + /** + * Power button cooldown period in milliseconds, after emergency gesture is triggered. A zero + * value means the cooldown period is disabled. + */ + private int mEmergencyGesturePowerButtonCooldownPeriodMs; + private long mLastPowerDown; + private long mLastEmergencyGestureTriggered; private int mPowerButtonConsecutiveTaps; private int mPowerButtonSlowConsecutiveTaps; private final UiEventLogger mUiEventLogger; @@ -210,6 +231,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); + updateEmergencyGesturePowerButtonCooldownPeriodMs(); mUserId = ActivityManager.getCurrentUser(); mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED)); @@ -230,6 +252,10 @@ public class GestureLauncherService extends SystemService { mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.EMERGENCY_GESTURE_ENABLED), false, mSettingObserver, mUserId); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS), + false, mSettingObserver, mUserId); } private void updateCameraRegistered() { @@ -263,6 +289,14 @@ public class GestureLauncherService extends SystemService { } } + @VisibleForTesting + void updateEmergencyGesturePowerButtonCooldownPeriodMs() { + int cooldownPeriodMs = getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, mUserId); + synchronized (this) { + mEmergencyGesturePowerButtonCooldownPeriodMs = cooldownPeriodMs; + } + } + private void unregisterCameraLaunchGesture() { if (mCameraLaunchRegistered) { mCameraLaunchRegistered = false; @@ -398,6 +432,20 @@ public class GestureLauncherService extends SystemService { } /** + * Gets power button cooldown period in milliseconds after emergency gesture is triggered. The + * value is capped at a maximum + * {@link GestureLauncherService#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX}. If the + * value is zero, it means the cooldown period is disabled. + */ + @VisibleForTesting + static int getEmergencyGesturePowerButtonCooldownPeriodMs(Context context, int userId) { + int cooldown = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS, + EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT, userId); + return Math.min(cooldown, EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX); + } + + /** * Whether to enable the camera launch gesture. */ private static boolean isCameraLaunchEnabled(Resources resources) { @@ -445,10 +493,24 @@ public class GestureLauncherService extends SystemService { */ public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive, MutableBoolean outLaunched) { + if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0 + && event.getEventTime() - mLastEmergencyGestureTriggered + < mEmergencyGesturePowerButtonCooldownPeriodMs) { + Slog.i(TAG, String.format( + "Suppressing power button: within %dms cooldown period after Emergency " + + "Gesture. Begin=%dms, end=%dms.", + mEmergencyGesturePowerButtonCooldownPeriodMs, + mLastEmergencyGestureTriggered, + mLastEmergencyGestureTriggered + mEmergencyGesturePowerButtonCooldownPeriodMs)); + outLaunched.value = false; + return true; + } + if (event.isLongPress()) { // Long presses are sent as a second key down. If the long press threshold is set lower // than the double tap of sequence interval thresholds, this could cause false double // taps or consecutive taps, so we want to ignore the long press event. + outLaunched.value = false; return false; } boolean launchCamera = false; @@ -509,6 +571,12 @@ public class GestureLauncherService extends SystemService { Slog.i(TAG, "Emergency gesture detected, launching."); launchEmergencyGesture = handleEmergencyGesture(); mUiEventLogger.log(GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER); + // Record emergency trigger time if emergency UI was launched + if (launchEmergencyGesture) { + synchronized (this) { + mLastEmergencyGestureTriggered = event.getEventTime(); + } + } } mMetricsLogger.histogram("power_consecutive_short_tap_count", mPowerButtonSlowConsecutiveTaps); @@ -600,6 +668,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); + updateEmergencyGesturePowerButtonCooldownPeriodMs(); } } }; @@ -610,6 +679,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); + updateEmergencyGesturePowerButtonCooldownPeriodMs(); } } }; diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java index 2eb9e34b3fd0..a0d86c9268fb 100644 --- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java @@ -187,6 +187,30 @@ public class GestureLauncherServiceTest { } @Test + public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_enabled() { + withEmergencyGesturePowerButtonCooldownPeriodMsValue(4000); + assertEquals(4000, + mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, + FAKE_USER_ID)); + } + + @Test + public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_disabled() { + withEmergencyGesturePowerButtonCooldownPeriodMsValue(0); + assertEquals(0, + mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, + FAKE_USER_ID)); + } + + @Test + public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_cappedAtMaximum() { + withEmergencyGesturePowerButtonCooldownPeriodMsValue(10000); + assertEquals(GestureLauncherService.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX, + mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, + FAKE_USER_ID)); + } + + @Test public void testHandleCameraLaunchGesture_userSetupComplete() { withUserSetupCompleteValue(true); @@ -645,6 +669,211 @@ public class GestureLauncherServiceTest { } @Test + public void testInterceptPowerKeyDown_triggerEmergency_singleTaps_cooldownTriggered() { + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent single tap is intercepted, but should not trigger any gesture + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + + // Add enough interval to reset consecutive tap count + interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Another single tap should be the same (intercepted but should not trigger gesture) + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + interactive = true; + outLaunched = new MutableBoolean(true); + intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + } + + @Test + public void + testInterceptPowerKeyDown_triggerEmergency_cameraGestureEnabled_doubleTap_cooldownTriggered() { + // Enable camera double tap gesture + withCameraDoubleTapPowerEnableConfigValue(true); + withCameraDoubleTapPowerDisableSettingValue(0); + mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent double tap is intercepted, but should not trigger any gesture + for (int i = 0; i < 2; i++) { + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, + IGNORED_CODE, IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, + interactive, outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; + eventTime += interval; + } + } + + @Test + public void testInterceptPowerKeyDown_triggerEmergency_fiveTaps_cooldownTriggered() { + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent 5 taps are intercepted, but should not trigger any gesture + for (int i = 0; i < 5; i++) { + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, + IGNORED_CODE, IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, + interactive, outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; + eventTime += interval; + } + } + + @Test + public void testInterceptPowerKeyDown_triggerEmergency_longPress_cooldownTriggered() { + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent long press is intercepted, but should not trigger any gesture + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, + KeyEvent.FLAG_LONG_PRESS); + + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + } + + @Test + public void testInterceptPowerKeyDown_triggerEmergency_cooldownDisabled_cooldownNotTriggered() { + // Disable power button cooldown by setting cooldown period to 0 + withEmergencyGesturePowerButtonCooldownPeriodMsValue(0); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent single tap is NOT intercepted + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + + // Add enough interval to reset consecutive tap count + interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Long press also NOT intercepted + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, + KeyEvent.FLAG_LONG_PRESS); + interactive = true; + outLaunched = new MutableBoolean(true); + intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + } + + @Test + public void + testInterceptPowerKeyDown_triggerEmergency_outsideCooldownPeriod_cooldownNotTriggered() { + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(5000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to be outside of cooldown period + long interval = 5001; + eventTime += interval; + + // Subsequent single tap is NOT intercepted + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + + // Add enough interval to reset consecutive tap count + interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Long press also NOT intercepted + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, + KeyEvent.FLAG_LONG_PRESS); + interactive = true; + outLaunched = new MutableBoolean(true); + intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + } + + @Test public void testInterceptPowerKeyDown_longpress() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); @@ -1153,6 +1382,45 @@ public class GestureLauncherServiceTest { assertEquals(1, tapCounts.get(1).intValue()); } + /** + * Helper method to trigger emergency gesture by pressing button for 5 times. + * @return last event time. + */ + private long triggerEmergencyGesture() { + // Enable emergency power gesture + withEmergencyGestureEnabledConfigValue(true); + withEmergencyGestureEnabledSettingValue(true); + mGestureLauncherService.updateEmergencyGestureEnabled(); + withUserSetupCompleteValue(true); + + // 4 button presses + long eventTime = INITIAL_EVENT_TIME_MILLIS; + boolean interactive = true; + KeyEvent keyEvent; + MutableBoolean outLaunched = new MutableBoolean(false); + for (int i = 0; i < 4; i++) { + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); + final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; + eventTime += interval; + } + + // 5th button press should trigger the emergency flow + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + outLaunched.value = false; + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertTrue(outLaunched.value); + assertTrue(intercepted); + verify(mUiEventLogger, times(1)) + .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER); + verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected(); + + return eventTime; + } + private void withCameraDoubleTapPowerEnableConfigValue(boolean enableConfigValue) { when(mResources.getBoolean( com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled)) @@ -1181,6 +1449,14 @@ public class GestureLauncherServiceTest { UserHandle.USER_CURRENT); } + private void withEmergencyGesturePowerButtonCooldownPeriodMsValue(int period) { + Settings.Secure.putIntForUser( + mContentResolver, + Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS, + period, + UserHandle.USER_CURRENT); + } + private void withUserSetupCompleteValue(boolean userSetupComplete) { int userSetupCompleteValue = userSetupComplete ? 1 : 0; Settings.Secure.putIntForUser( |