Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c22b2a95 authored by Patrick Huang's avatar Patrick Huang
Browse files

Add power button cooldown after emergency gesture is triggered.

Bug: 195782998
Test: unit tests, manual test on device
Change-Id: I80dbfd5e297af6e62ecbd6593dd63686c1b7ac1c
parent a933cf1b
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -9326,6 +9326,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.
+2 −0
Original line number Diff line number Diff line
@@ -276,6 +276,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);
+70 −0
Original line number Diff line number Diff line
@@ -87,6 +87,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.
     */
@@ -163,7 +177,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;
@@ -231,6 +252,7 @@ public class GestureLauncherService extends SystemService {
            updateCameraRegistered();
            updateCameraDoubleTapPowerEnabled();
            updateEmergencyGestureEnabled();
            updateEmergencyGesturePowerButtonCooldownPeriodMs();

            mUserId = ActivityManager.getCurrentUser();
            mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
@@ -261,6 +283,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() {
@@ -294,6 +320,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;
@@ -427,6 +461,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.
     */
@@ -475,10 +523,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;
@@ -542,6 +604,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);
@@ -670,6 +738,7 @@ public class GestureLauncherService extends SystemService {
                updateCameraRegistered();
                updateCameraDoubleTapPowerEnabled();
                updateEmergencyGestureEnabled();
                updateEmergencyGesturePowerButtonCooldownPeriodMs();
            }
        }
    };
@@ -680,6 +749,7 @@ public class GestureLauncherService extends SystemService {
                updateCameraRegistered();
                updateCameraDoubleTapPowerEnabled();
                updateEmergencyGestureEnabled();
                updateEmergencyGesturePowerButtonCooldownPeriodMs();
            }
        }
    };
+276 −0
Original line number Diff line number Diff line
@@ -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);
@@ -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);
@@ -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(