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

Commit ed4e0aa7 authored by Charles Wang's avatar Charles Wang
Browse files

Allow focused privileged windows to capture the power button event.

When this feature enabled, focused windows with the
OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission and the window
flag INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS set will receive KEYCODE_POWER KeyEvents.

The window can override the double-tap gesture default behavior (launching
camera) by handling the second onKeyDown event of a double tap. If the
app does not handle this event, or if the windw does not have the
permissions to receive KEYCODE_POWER, the default behavior of launching
camera is performed.

A double tap is defined as two consecutive KEYCODE_POWER key down events
within a 300 ms threshold.

Single, long-press and non double-press gestures behaviors will all be
default behavior, regardless of app handling the events or not.

Turning display on and off is unaffected, with the following exception:
In a privileged, focused window that doesn't handle the KEYCODE_POWER
event, there will be a 300 ms delay in turning off the screen.

More details at go/power-button-dd.

Bug: 357144512
Test: atest WmTests:PowerKeyGestureTests
Test: manual testing (single, long-press, double-press, 5x press of
power button on non-privileged windows and privileged windows that
handle and don't handle the KEYCODE_POWER KeyEvent.)
Flag: com.android.hardware.input.override_power_key_behavior_in_focused_window

Change-Id: Ia21ebbda4a93ade5f4d5f73fe0f3bf659e36647a
parent 8da65957
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -27,11 +27,13 @@ public class KeyInterceptionInfo {
    // Debug friendly name to help identify the window
    public final String windowTitle;
    public final int windowOwnerUid;
    public final int inputFeaturesFlags;

    public KeyInterceptionInfo(int type, int flags, String title, int uid) {
    public KeyInterceptionInfo(int type, int flags, String title, int uid, int inputFeaturesFlags) {
        layoutParamsType = type;
        layoutParamsPrivateFlags = flags;
        windowTitle = title;
        windowOwnerUid = uid;
        this.inputFeaturesFlags = inputFeaturesFlags;
    }
}
+48 −5
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server;

import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.internal.R.integer.config_defaultMinEmergencyGestureTapDurationMillis;

import android.app.ActivityManager;
@@ -103,7 +104,7 @@ public class GestureLauncherService extends SystemService {
    /**
     * Number of taps required to launch camera shortcut.
     */
    private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
    public static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;

    /** The listener that receives the gesture event. */
    private final GestureEventListener mGestureListener = new GestureEventListener();
@@ -208,7 +209,9 @@ public class GestureLauncherService extends SystemService {
    }

    @VisibleForTesting
    GestureLauncherService(Context context, MetricsLogger metricsLogger,
    public GestureLauncherService(
            Context context,
            MetricsLogger metricsLogger,
            UiEventLogger uiEventLogger) {
        super(context);
        mContext = context;
@@ -500,6 +503,46 @@ public class GestureLauncherService extends SystemService {
                || isEmergencyGestureEnabled(resources);
    }

    /**
     * Processes a power key event in GestureLauncherService without performing an action. This
     * method is called on every KEYCODE_POWER ACTION_DOWN event and ensures that, even if
     * KEYCODE_POWER events are passed to and handled by the app, the GestureLauncherService still
     * keeps track of all running KEYCODE_POWER events for its gesture detection and relevant
     * actions.
     */
    public void processPowerKeyDown(KeyEvent event) {
        if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
                && event.getEventTime() - mLastEmergencyGestureTriggered
                < mEmergencyGesturePowerButtonCooldownPeriodMs) {
            return;
        }
        if (event.isLongPress()) {
            return;
        }

        final long powerTapInterval;

        synchronized (this) {
            powerTapInterval = event.getEventTime() - mLastPowerDown;
            mLastPowerDown = event.getEventTime();
            if (powerTapInterval >= POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) {
                // Tap too slow, reset consecutive tap counts.
                mFirstPowerDown = event.getEventTime();
                mPowerButtonConsecutiveTaps = 1;
                mPowerButtonSlowConsecutiveTaps = 1;
            } else if (powerTapInterval >= CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
                // Tap too slow for shortcuts
                mFirstPowerDown = event.getEventTime();
                mPowerButtonConsecutiveTaps = 1;
                mPowerButtonSlowConsecutiveTaps++;
            } else if (powerTapInterval > 0) {
                // Fast consecutive tap
                mPowerButtonConsecutiveTaps++;
                mPowerButtonSlowConsecutiveTaps++;
            }
        }
    }

    /**
     * Attempts to intercept power key down event by detecting certain gesture patterns
     *
@@ -507,8 +550,8 @@ public class GestureLauncherService extends SystemService {
     * @param outLaunched true if some action is taken as part of the key intercept (eg, app launch)
     * @return true if the key down event is intercepted
     */
    public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
            MutableBoolean outLaunched) {
    public boolean interceptPowerKeyDown(
            KeyEvent event, boolean interactive, MutableBoolean outLaunched) {
        if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
                && event.getEventTime() - mLastEmergencyGestureTriggered
                < mEmergencyGesturePowerButtonCooldownPeriodMs) {
@@ -546,7 +589,7 @@ public class GestureLauncherService extends SystemService {
                mFirstPowerDown  = event.getEventTime();
                mPowerButtonConsecutiveTaps = 1;
                mPowerButtonSlowConsecutiveTaps++;
            } else {
            } else if (!overridePowerKeyBehaviorInFocusedWindow() || powerTapInterval > 0) {
                // Fast consecutive tap
                mPowerButtonConsecutiveTaps++;
                mPowerButtonSlowConsecutiveTaps++;
+227 −5
Original line number Diff line number Diff line
@@ -88,10 +88,12 @@ import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGest
import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.GestureLauncherService.CAMERA_POWER_TAP_COUNT_THRESHOLD;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -431,6 +433,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
    private static final String ACTION_VOICE_ASSIST_RETAIL =
            "android.intent.action.VOICE_ASSIST_RETAIL";

    /**
     * Maximum amount of time in milliseconds between consecutive power onKeyDown events to be
     * considered a multi-press, only used for the power button.
     * Note: To maintain backwards compatibility for the power button, we are measuring the times
     * between consecutive down events instead of the first tap's up event and the second tap's
     * down event.
     */
    @VisibleForTesting public static final int POWER_MULTI_PRESS_TIMEOUT_MILLIS =
            ViewConfiguration.getMultiPressTimeout();

    /**
     * Lock protecting internal state.  Must not call out into window
     * manager with lock held.  (This lock will be acquired in places
@@ -492,6 +504,32 @@ public class PhoneWindowManager implements WindowManagerPolicy {

    private WindowWakeUpPolicy mWindowWakeUpPolicy;

    /**
     * The three variables below are used for custom power key gesture detection in
     * PhoneWindowManager. They are used to detect when the power button has been double pressed
     * and, when it does happen, makes the behavior overrideable by the app.
     *
     * We cannot use the {@link PowerKeyRule} for this because multi-press power gesture detection
     * and behaviors are handled by {@link com.android.server.GestureLauncherService}, and the
     * {@link PowerKeyRule} only handles single and long-presses of the power button. As a result,
     * overriding the double tap behavior requires custom gesture detection here that mimics the
     * logic in {@link com.android.server.GestureLauncherService}.
     *
     * Long-term, it would be beneficial to move all power gesture detection to
     * {@link PowerKeyRule} so that this custom logic isn't required.
     */
    // Time of last power down event.
    private long mLastPowerDown;

    // Number of power button events consecutively triggered (within a specific timeout threshold).
    private int mPowerButtonConsecutiveTaps = 0;

    // Whether a double tap of the power button has been detected.
    volatile boolean mDoubleTapPowerDetected;

    // Runnable that is queued on a delay when the first power keyDown event is sent to the app.
    private Runnable mPowerKeyDelayedRunnable = null;

    boolean mSafeMode;

    // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
@@ -1097,6 +1135,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        mPowerKeyHandled = mPowerKeyHandled || hungUp
                || handledByPowerManager || isKeyGestureTriggered
                || mKeyCombinationManager.isPowerKeyIntercepted();

        if (overridePowerKeyBehaviorInFocusedWindow()) {
            mPowerKeyHandled |= mDoubleTapPowerDetected;
        }

        if (!mPowerKeyHandled) {
            if (!interactive) {
                wakeUpFromWakeKey(event);
@@ -2669,9 +2712,21 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            if (mShouldEarlyShortPressOnPower) {
                return;
            }
            // TODO(b/380433365): Remove deferring single power press action when refactoring.
            if (overridePowerKeyBehaviorInFocusedWindow()) {
                mDeferredKeyActionExecutor.cancelQueuedAction(KEYCODE_POWER);
                mDeferredKeyActionExecutor.queueKeyAction(
                        KEYCODE_POWER,
                        downTime,
                        () -> {
                            powerPress(downTime, 1 /*count*/, displayId);
                        });
            } else {
                powerPress(downTime, 1 /*count*/, displayId);
            }

        }

        @Override
        long getLongPressTimeoutMs() {
            if (getResolvedLongPressOnPowerBehavior() == LONG_PRESS_POWER_ASSISTANT) {
@@ -2700,8 +2755,18 @@ public class PhoneWindowManager implements WindowManagerPolicy {

        @Override
        void onMultiPress(long downTime, int count, int displayId) {
            if (overridePowerKeyBehaviorInFocusedWindow()) {
                mDeferredKeyActionExecutor.cancelQueuedAction(KEYCODE_POWER);
                mDeferredKeyActionExecutor.queueKeyAction(
                        KEYCODE_POWER,
                        downTime,
                        () -> {
                            powerPress(downTime, count, displayId);
                        });
            } else {
                powerPress(downTime, count, displayId);
            }
        }

        @Override
        void onKeyUp(long eventTime, int count, int displayId, int deviceId, int metaState) {
@@ -3477,6 +3542,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            }
        }

        if (overridePowerKeyBehaviorInFocusedWindow() && event.getKeyCode() == KEYCODE_POWER
                && event.getAction() == KeyEvent.ACTION_UP
                && mDoubleTapPowerDetected) {
            mDoubleTapPowerDetected = false;
        }

        return needToConsumeKey ? keyConsumed : keyNotConsumed;
    }

@@ -3992,6 +4063,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    sendSystemKeyToStatusBarAsync(event);
                    return true;
                }
            case KeyEvent.KEYCODE_POWER:
                return interceptPowerKeyBeforeDispatching(focusedToken, event);
            case KeyEvent.KEYCODE_SCREENSHOT:
                if (firstDown) {
                    interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
@@ -4047,6 +4120,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    sendSystemKeyToStatusBarAsync(event);
                    return true;
                }
            case KeyEvent.KEYCODE_POWER:
                return interceptPowerKeyBeforeDispatching(focusedToken, event);
        }
        if (isValidGlobalKey(keyCode)
                && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -4057,6 +4132,90 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        return (metaState & KeyEvent.META_META_ON) != 0;
    }

    /**
     * Called by interceptKeyBeforeDispatching to handle interception logic for KEYCODE_POWER
     * KeyEvents.
     *
     * @return true if intercepting the key, false if sending to app.
     */
    private boolean interceptPowerKeyBeforeDispatching(IBinder focusedToken, KeyEvent event) {
        if (!overridePowerKeyBehaviorInFocusedWindow()) {
            //Flag disabled: intercept the power key and do not send to app.
            return true;
        }
        if (event.getKeyCode() != KEYCODE_POWER) {
            Log.wtf(TAG, "interceptPowerKeyBeforeDispatching received a non-power KeyEvent "
                    + "with key code: " + event.getKeyCode());
            return false;
        }

        // Intercept keys (don't send to app) for 3x, 4x, 5x gestures)
        if (mPowerButtonConsecutiveTaps > CAMERA_POWER_TAP_COUNT_THRESHOLD) {
            setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, event.getDownTime());
            return true;
        }

        // UP key; just reuse the original decision.
        if (event.getAction() == KeyEvent.ACTION_UP) {
            final Set<Integer> consumedKeys = mConsumedKeysForDevice.get(event.getDeviceId());
            return consumedKeys != null
                    && consumedKeys.contains(event.getKeyCode());
        }

        KeyInterceptionInfo info =
                mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);

        if (info == null || !mButtonOverridePermissionChecker.canWindowOverridePowerKey(mContext,
                info.windowOwnerUid, info.inputFeaturesFlags)) {
            // The focused window does not have the permission to override power key behavior.
            if (DEBUG_INPUT) {
                String interceptReason = "";
                if (info == null) {
                    interceptReason = "Window is null";
                } else if (!mButtonOverridePermissionChecker.canAppOverrideSystemKey(mContext,
                        info.windowOwnerUid)) {
                    interceptReason = "Application does not have "
                            + "OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission";
                } else {
                    interceptReason = "Window does not have inputFeatureFlag set";
                }

                Log.d(TAG, String.format("Intercepting KEYCODE_POWER event. action=%d, "
                                + "eventTime=%d to window=%s. interceptReason=%s. "
                                + "mDoubleTapPowerDetected=%b",
                        event.getAction(), event.getEventTime(), (info != null)
                                ? info.windowTitle : "null", interceptReason,
                        mDoubleTapPowerDetected));
            }
            // Intercept the key (i.e. do not send to app)
            setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, event.getDownTime());
            return true;
        }

        if (DEBUG_INPUT) {
            Log.d(TAG, String.format("Sending KEYCODE_POWER to app. action=%d, "
                            + "eventTime=%d to window=%s. mDoubleTapPowerDetected=%b",
                    event.getAction(), event.getEventTime(), info.windowTitle,
                    mDoubleTapPowerDetected));
        }

        if (!mDoubleTapPowerDetected) {
            //Single press: post a delayed runnable for the single press power action that will be
            // called if it's not cancelled by a double press.
            final var downTime = event.getDownTime();
            mPowerKeyDelayedRunnable = () ->
                    setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, downTime);
            mHandler.postDelayed(mPowerKeyDelayedRunnable, POWER_MULTI_PRESS_TIMEOUT_MILLIS);
        } else if (mPowerKeyDelayedRunnable != null) {
            //Double press detected: cancel the single press runnable.
            mHandler.removeCallbacks(mPowerKeyDelayedRunnable);
            mPowerKeyDelayedRunnable = null;
        }

        // Focused window has permission. Send to app.
        return false;
    }

    @SuppressLint("MissingPermission")
    private void initKeyGestures() {
        if (!useKeyGestureEventHandler()) {
@@ -4631,6 +4790,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            return true;
        }

        if (overridePowerKeyBehaviorInFocusedWindow() && keyCode == KEYCODE_POWER) {
            handleUnhandledSystemKey(event);
            return true;
        }

        if (useKeyGestureEventHandler()) {
            return false;
        }
@@ -5465,8 +5629,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                        KeyEvent.actionToString(event.getAction()),
                        mPowerKeyHandled ? 1 : 0,
                        mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
                if (overridePowerKeyBehaviorInFocusedWindow()) {
                    result |= ACTION_PASS_TO_USER;
                } else {
                    // Any activity on the power button stops the accessibility shortcut
                    result &= ~ACTION_PASS_TO_USER;
                }

                isWakeKey = false; // wake-up will be handled separately
                if (down) {
                    interceptPowerKeyDown(event, interactiveAndAwake, isKeyGestureTriggered);
@@ -5728,6 +5897,32 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

        if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
            if (overridePowerKeyBehaviorInFocusedWindow()) {
                if (mGestureLauncherService != null) {
                    mGestureLauncherService.processPowerKeyDown(event);
                }

                if (detectDoubleTapPower(event)) {
                    mDoubleTapPowerDetected = true;

                    // Copy of the event for handler in case the original event gets recycled.
                    KeyEvent eventCopy = KeyEvent.obtain(event);
                    mDeferredKeyActionExecutor.queueKeyAction(
                            KeyEvent.KEYCODE_POWER,
                            eventCopy.getEventTime(),
                            () -> {
                                if (!handleCameraGesture(eventCopy, interactive)) {
                                    mSingleKeyGestureDetector.interceptKey(
                                            eventCopy, interactive, defaultDisplayOn);
                                } else {
                                    mSingleKeyGestureDetector.reset();
                                }
                                eventCopy.recycle();
                            });
                    return;
                }
            }

            mPowerKeyHandled = handleCameraGesture(event, interactive);
            if (mPowerKeyHandled) {
                // handled by camera gesture.
@@ -5739,6 +5934,27 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        mSingleKeyGestureDetector.interceptKey(event, interactive, defaultDisplayOn);
    }

    private boolean detectDoubleTapPower(KeyEvent event) {
        if (event.getKeyCode() != KEYCODE_POWER || event.getAction() != KeyEvent.ACTION_DOWN) {
            return false;
        }
        if (event.isLongPress()) {
            return false;
        }

        final long powerTapInterval = event.getEventTime() - mLastPowerDown;
        mLastPowerDown = event.getEventTime();
        if (powerTapInterval >= POWER_MULTI_PRESS_TIMEOUT_MILLIS) {
            // Tap too slow for double press
            mPowerButtonConsecutiveTaps = 1;
        } else {
            mPowerButtonConsecutiveTaps++;
        }

        return powerTapInterval < POWER_MULTI_PRESS_TIMEOUT_MILLIS
                && mPowerButtonConsecutiveTaps == CAMERA_POWER_TAP_COUNT_THRESHOLD;
    }

    // The camera gesture will be detected by GestureLauncherService.
    private boolean handleCameraGesture(KeyEvent event, boolean interactive) {
        // camera gesture.
@@ -7595,6 +7811,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    null)
                    == PERMISSION_GRANTED;
        }

        boolean canWindowOverridePowerKey(Context context, int uid, int inputFeaturesFlags) {
            return canAppOverrideSystemKey(context, uid)
                    && (inputFeaturesFlags & WindowManager.LayoutParams
                    .INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) != 0;
        }
    }

    private int getTargetDisplayIdForKeyEvent(KeyEvent event) {
+3 −2
Original line number Diff line number Diff line
@@ -5750,9 +5750,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
                || mKeyInterceptionInfo.layoutParamsPrivateFlags != getAttrs().privateFlags
                || mKeyInterceptionInfo.layoutParamsType != getAttrs().type
                || mKeyInterceptionInfo.windowTitle != getWindowTag()
                || mKeyInterceptionInfo.windowOwnerUid != getOwningUid()) {
                || mKeyInterceptionInfo.windowOwnerUid != getOwningUid()
                || mKeyInterceptionInfo.inputFeaturesFlags != getAttrs().inputFeatures) {
            mKeyInterceptionInfo = new KeyInterceptionInfo(getAttrs().type, getAttrs().privateFlags,
                    getWindowTag().toString(), getOwningUid());
                    getWindowTag().toString(), getOwningUid(), getAttrs().inputFeatures);
        }
        return mKeyInterceptionInfo;
    }
+69 −0
Original line number Diff line number Diff line
@@ -1408,6 +1408,47 @@ public class GestureLauncherServiceTest {
        assertEquals(1, tapCounts.get(1).intValue());
    }

    /**
     * If processPowerKeyDown is called instead of interceptPowerKeyDown (meaning the double tap
     * gesture isn't performed), the emergency gesture is still launched.
     */
    @Test
    public void
            testProcessPowerKeyDown_fiveInboundPresses_cameraDoesNotLaunch_emergencyGestureLaunches() {
        enableCameraGesture();
        enableEmergencyGesture();

        // First event
        long eventTime = INITIAL_EVENT_TIME_MILLIS;
        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);

        //Second event; call processPowerKeyDown without calling interceptPowerKeyDown
        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
        eventTime += interval;
        KeyEvent keyEvent =
                new KeyEvent(
                        IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT);
        mGestureLauncherService.processPowerKeyDown(keyEvent);

        verify(mMetricsLogger, never())
                .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
        verify(mUiEventLogger, never()).log(any());

        // Presses 3 and 4 should not trigger any gesture
        for (int i = 0; i < 2; i++) {
            eventTime += interval;
            sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
        }

        // Fifth button press should still trigger the emergency flow
        eventTime += interval;
        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);

        verify(mUiEventLogger, times(1))
                .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
        verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
    }

    /**
     * Helper method to trigger emergency gesture by pressing button for 5 times.
     *
@@ -1510,4 +1551,32 @@ public class GestureLauncherServiceTest {
                userSetupCompleteValue,
                UserHandle.USER_CURRENT);
    }


    private void enableEmergencyGesture() {
        withEmergencyGestureEnabledConfigValue(true);
        withEmergencyGestureEnabledSettingValue(true);
        mGestureLauncherService.updateEmergencyGestureEnabled();
        withUserSetupCompleteValue(true);
    }

    private void enableCameraGesture() {
        withCameraDoubleTapPowerEnableConfigValue(true);
        withCameraDoubleTapPowerDisableSettingValue(0);
        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
        withUserSetupCompleteValue(true);
    }

    private void sendPowerKeyDownToGestureLauncherServiceAndAssertValues(
            long eventTime, boolean expectedIntercept, boolean expectedOutLaunchedValue) {
        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);
        assertEquals(intercepted, expectedIntercept);
        assertEquals(outLaunched.value, expectedOutLaunchedValue);
    }
}
Loading