Loading core/java/android/provider/Settings.java +10 −0 Original line number Diff line number Diff line Loading @@ -9194,6 +9194,16 @@ public final class Settings { public static final String EMERGENCY_GESTURE_SOUND_ENABLED = "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. Loading packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +2 −0 Original line number Diff line number Diff line Loading @@ -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); Loading services/core/java/com/android/server/GestureLauncherService.java +70 −0 Original line number Diff line number Diff line Loading @@ -82,6 +82,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. */ Loading Loading @@ -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; Loading Loading @@ -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)); Loading @@ -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() { Loading Loading @@ -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; Loading Loading @@ -397,6 +431,20 @@ public class GestureLauncherService extends SystemService { Settings.Secure.EMERGENCY_GESTURE_ENABLED, 1, userId) != 0; } /** * 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. */ Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -600,6 +668,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); updateEmergencyGesturePowerButtonCooldownPeriodMs(); } } }; Loading @@ -610,6 +679,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); updateEmergencyGesturePowerButtonCooldownPeriodMs(); } } }; Loading services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +276 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,30 @@ public class GestureLauncherServiceTest { mContext, FAKE_USER_ID)); } @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); Loading Loading @@ -644,6 +668,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); Loading Loading @@ -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)) Loading Loading @@ -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( Loading Loading
core/java/android/provider/Settings.java +10 −0 Original line number Diff line number Diff line Loading @@ -9194,6 +9194,16 @@ public final class Settings { public static final String EMERGENCY_GESTURE_SOUND_ENABLED = "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. Loading
packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +2 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
services/core/java/com/android/server/GestureLauncherService.java +70 −0 Original line number Diff line number Diff line Loading @@ -82,6 +82,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. */ Loading Loading @@ -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; Loading Loading @@ -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)); Loading @@ -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() { Loading Loading @@ -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; Loading Loading @@ -397,6 +431,20 @@ public class GestureLauncherService extends SystemService { Settings.Secure.EMERGENCY_GESTURE_ENABLED, 1, userId) != 0; } /** * 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. */ Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -600,6 +668,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); updateEmergencyGesturePowerButtonCooldownPeriodMs(); } } }; Loading @@ -610,6 +679,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); updateEmergencyGesturePowerButtonCooldownPeriodMs(); } } }; Loading
services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +276 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,30 @@ public class GestureLauncherServiceTest { mContext, FAKE_USER_ID)); } @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); Loading Loading @@ -644,6 +668,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); Loading Loading @@ -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)) Loading Loading @@ -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( Loading