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

Commit 392b900a authored by Charles Wang's avatar Charles Wang
Browse files

Launch wallet app on double tap option.

Reads user settings on whether to launch camera or wallet on power
button double tap. If wallet has the setting, send PendingIntent retrieved from
QuickAccessWalletClient.getGestureTargetActivityPendingIntent.

Bug: 378469025
Test: atest GestureLauncherServiceTest
Test: Manual testing (double-tap power button with both settings and
ensure camera/wallet are appropriately launched.)
Flag: android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap
Change-Id: Id14db9f33cd6f0de7527b06747d9520c3478a742

Change-Id: I808769cc5ee012a172427f98f585c025c516a386
parent ed4e0aa7
Loading
Loading
Loading
Loading
+217 −43
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@

package com.android.server;

import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;

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

import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -34,6 +38,7 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
@@ -42,10 +47,12 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
@@ -71,7 +78,8 @@ public class GestureLauncherService extends SystemService {
     * Time in milliseconds in which the power button must be pressed twice so it will be considered
     * as a camera launch.
     */
    @VisibleForTesting static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
    @VisibleForTesting
    static final long POWER_DOUBLE_TAP_MAX_TIME_MS = 300;


    /**
@@ -101,10 +109,23 @@ public class GestureLauncherService extends SystemService {
    @VisibleForTesting
    static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000;

    /**
     * Number of taps required to launch camera shortcut.
     */
    public static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
    /** Indicates camera should be launched on power double tap. */
    @VisibleForTesting static final int LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER = 0;

    /** Indicates wallet should be launched on power double tap. */
    @VisibleForTesting static final int LAUNCH_WALLET_ON_DOUBLE_TAP_POWER = 1;

    /** Number of taps required to launch the double tap shortcut (either camera or wallet). */
    public static final int DOUBLE_POWER_TAP_COUNT_THRESHOLD = 2;

    /** Bundle to send with PendingIntent to grant background activity start privileges. */
    private static final Bundle GRANT_BACKGROUND_START_PRIVILEGES =
            ActivityOptions.makeBasic()
                    .setPendingIntentBackgroundActivityStartMode(
                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS)
                    .toBundle();

    private final QuickAccessWalletClient mQuickAccessWalletClient;

    /** The listener that receives the gesture event. */
    private final GestureEventListener mGestureListener = new GestureEventListener();
@@ -159,6 +180,9 @@ public class GestureLauncherService extends SystemService {
     */
    private boolean mCameraDoubleTapPowerEnabled;

    /** Whether wallet double tap power button gesture is currently enabled. */
    private boolean mWalletDoubleTapPowerEnabled;

    /**
     * Whether emergency gesture is currently enabled
     */
@@ -205,16 +229,22 @@ public class GestureLauncherService extends SystemService {
        }
    }
    public GestureLauncherService(Context context) {
        this(context, new MetricsLogger(), new UiEventLoggerImpl());
        this(
                context,
                new MetricsLogger(),
                QuickAccessWalletClient.create(context),
                new UiEventLoggerImpl());
    }

    @VisibleForTesting
    public GestureLauncherService(
            Context context,
            MetricsLogger metricsLogger,
            QuickAccessWalletClient quickAccessWalletClient,
            UiEventLogger uiEventLogger) {
        super(context);
        mContext = context;
        mQuickAccessWalletClient = quickAccessWalletClient;
        mMetricsLogger = metricsLogger;
        mUiEventLogger = uiEventLogger;
    }
@@ -240,6 +270,9 @@ public class GestureLauncherService extends SystemService {
                    "GestureLauncherService");
            updateCameraRegistered();
            updateCameraDoubleTapPowerEnabled();
            if (launchWalletOptionOnPowerDoubleTap()) {
                updateWalletDoubleTapPowerEnabled();
            }
            updateEmergencyGestureEnabled();
            updateEmergencyGesturePowerButtonCooldownPeriodMs();

@@ -294,6 +327,14 @@ public class GestureLauncherService extends SystemService {
        }
    }

    @VisibleForTesting
    void updateWalletDoubleTapPowerEnabled() {
        boolean enabled = isWalletDoubleTapPowerSettingEnabled(mContext, mUserId);
        synchronized (this) {
            mWalletDoubleTapPowerEnabled = enabled;
        }
    }

    @VisibleForTesting
    void updateEmergencyGestureEnabled() {
        boolean enabled = isEmergencyGestureSettingEnabled(mContext, mUserId);
@@ -421,11 +462,34 @@ public class GestureLauncherService extends SystemService {
                        Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
    }

    /** Checks if camera should be launched on double press of the power button. */
    public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
        return isCameraDoubleTapPowerEnabled(context.getResources())
        boolean res;

        if (launchWalletOptionOnPowerDoubleTap()) {
            res = isDoubleTapPowerGestureSettingEnabled(context, userId)
                    && getDoubleTapPowerGestureAction(context, userId)
                    == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
        } else {
            // These are legacy settings that will be deprecated once the option to launch both
            // wallet and camera has been created.
            res = isCameraDoubleTapPowerEnabled(context.getResources())
                    && (Settings.Secure.getIntForUser(context.getContentResolver(),
                    Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
        }
        return res;
    }

    /** Checks if wallet should be launched on double tap of the power button. */
    public static boolean isWalletDoubleTapPowerSettingEnabled(Context context, int userId) {
        if (!launchWalletOptionOnPowerDoubleTap()) {
            return false;
        }

        return isDoubleTapPowerGestureSettingEnabled(context, userId)
                && getDoubleTapPowerGestureAction(context, userId)
                        == LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
    }

    public static boolean isCameraLiftTriggerSettingEnabled(Context context, int userId) {
        return isCameraLiftTriggerEnabled(context.getResources())
@@ -444,6 +508,28 @@ public class GestureLauncherService extends SystemService {
                isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
    }

    private static int getDoubleTapPowerGestureAction(Context context, int userId) {
        return Settings.Secure.getIntForUser(
                context.getContentResolver(),
                Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
                LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER,
                userId);
    }

    /** Whether the shortcut to launch app on power double press is enabled. */
    private static boolean isDoubleTapPowerGestureSettingEnabled(Context context, int userId) {
        return Settings.Secure.getIntForUser(
                                context.getContentResolver(),
                                Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
                                isDoubleTapConfigEnabled(context.getResources()) ? 1 : 0,
                                userId)
                        == 1;
    }

    private static boolean isDoubleTapConfigEnabled(Resources resources) {
        return resources.getBoolean(R.bool.config_doubleTapPowerGestureEnabled);
    }

    /**
     * Gets power button cooldown period in milliseconds after emergency gesture is triggered. The
     * value is capped at a maximum
@@ -497,10 +583,16 @@ public class GestureLauncherService extends SystemService {
     * Whether GestureLauncherService should be enabled according to system properties.
     */
    public static boolean isGestureLauncherEnabled(Resources resources) {
        return isCameraLaunchEnabled(resources)
                || isCameraDoubleTapPowerEnabled(resources)
        boolean res =
                isCameraLaunchEnabled(resources)
                        || isCameraLiftTriggerEnabled(resources)
                        || isEmergencyGestureEnabled(resources);
        if (launchWalletOptionOnPowerDoubleTap()) {
            res |= isDoubleTapConfigEnabled(resources);
        } else {
            res |= isCameraDoubleTapPowerEnabled(resources);
        }
        return res;
    }

    /**
@@ -530,7 +622,7 @@ public class GestureLauncherService extends SystemService {
                mFirstPowerDown = event.getEventTime();
                mPowerButtonConsecutiveTaps = 1;
                mPowerButtonSlowConsecutiveTaps = 1;
            } else if (powerTapInterval >= CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
            } else if (powerTapInterval >= POWER_DOUBLE_TAP_MAX_TIME_MS) {
                // Tap too slow for shortcuts
                mFirstPowerDown = event.getEventTime();
                mPowerButtonConsecutiveTaps = 1;
@@ -573,6 +665,7 @@ public class GestureLauncherService extends SystemService {
            return false;
        }
        boolean launchCamera = false;
        boolean launchWallet = false;
        boolean launchEmergencyGesture = false;
        boolean intercept = false;
        long powerTapInterval;
@@ -584,7 +677,7 @@ public class GestureLauncherService extends SystemService {
                mFirstPowerDown  = event.getEventTime();
                mPowerButtonConsecutiveTaps = 1;
                mPowerButtonSlowConsecutiveTaps = 1;
            } else if (powerTapInterval >= CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
            } else if (powerTapInterval >= POWER_DOUBLE_TAP_MAX_TIME_MS) {
                // Tap too slow for shortcuts
                mFirstPowerDown  = event.getEventTime();
                mPowerButtonConsecutiveTaps = 1;
@@ -629,10 +722,16 @@ public class GestureLauncherService extends SystemService {
                }
            }
            if (mCameraDoubleTapPowerEnabled
                    && powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS
                    && mPowerButtonConsecutiveTaps == CAMERA_POWER_TAP_COUNT_THRESHOLD) {
                    && powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
                    && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
                launchCamera = true;
                intercept = interactive;
            } else if (launchWalletOptionOnPowerDoubleTap()
                    && mWalletDoubleTapPowerEnabled
                    && powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
                    && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
                launchWallet = true;
                intercept = interactive;
            }
        }
        if (mPowerButtonConsecutiveTaps > 1 || mPowerButtonSlowConsecutiveTaps > 1) {
@@ -651,6 +750,10 @@ public class GestureLauncherService extends SystemService {
                        (int) powerTapInterval);
                mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
            }
        } else if (launchWallet) {
            Slog.i(TAG, "Power button double tap gesture detected, launching wallet. Interval="
                    + powerTapInterval + "ms");
            launchWallet = sendGestureTargetActivityPendingIntent();
        } else if (launchEmergencyGesture) {
            Slog.i(TAG, "Emergency gesture detected, launching.");
            launchEmergencyGesture = handleEmergencyGesture();
@@ -666,11 +769,74 @@ public class GestureLauncherService extends SystemService {
                mPowerButtonSlowConsecutiveTaps);
        mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);

        outLaunched.value = launchCamera || launchEmergencyGesture;
        outLaunched.value = launchCamera || launchEmergencyGesture || launchWallet;
        // Intercept power key event if the press is part of a gesture (camera, eGesture) and the
        // user has completed setup.
        return intercept && isUserSetupComplete();
    }

    /**
     * Fetches and sends gestureTargetActivityPendingIntent from QuickAccessWallet, which is a
     * specific activity that QuickAccessWalletService has defined to be launch on detection of the
     * power button gesture.
     */
    private boolean sendGestureTargetActivityPendingIntent() {
        boolean userSetupComplete = isUserSetupComplete();
        if (mQuickAccessWalletClient == null
                || !mQuickAccessWalletClient.isWalletServiceAvailable()) {
            Slog.w(TAG, "QuickAccessWalletService is not available, ignoring wallet gesture.");
            return false;
        }

        if (!userSetupComplete) {
            if (DBG) {
                Slog.d(TAG, "userSetupComplete = false, ignoring wallet gesture.");
            }
            return false;
        }
        if (DBG) {
            Slog.d(TAG, "userSetupComplete = true, performing wallet gesture.");
        }

        mQuickAccessWalletClient.getGestureTargetActivityPendingIntent(
                getContext().getMainExecutor(),
                gesturePendingIntent -> {
                    if (gesturePendingIntent == null) {
                        Slog.d(TAG, "getGestureTargetActivityPendingIntent is null.");
                        sendFallbackPendingIntent();
                        return;
                    }
                    sendPendingIntentWithBackgroundStartPrivileges(gesturePendingIntent);
                });
        return true;
    }

    /**
     * If gestureTargetActivityPendingIntent is null, this method is invoked to start the activity
     * that QuickAccessWalletService has defined to host the Wallet view, which is typically the
     * home screen of the Wallet application.
     */
    private void sendFallbackPendingIntent() {
        mQuickAccessWalletClient.getWalletPendingIntent(
                getContext().getMainExecutor(),
                walletPendingIntent -> {
                    if (walletPendingIntent == null) {
                        Slog.w(TAG, "getWalletPendingIntent returns null. Not launching "
                                + "anything for wallet.");
                        return;
                    }
                    sendPendingIntentWithBackgroundStartPrivileges(walletPendingIntent);
                });
    }

    private void sendPendingIntentWithBackgroundStartPrivileges(PendingIntent pendingIntent) {
        try {
            pendingIntent.send(GRANT_BACKGROUND_START_PRIVILEGES);
        } catch (PendingIntent.CanceledException e) {
            Slog.e(TAG, "PendingIntent was canceled", e);
        }
    }

    /**
     * @return true if camera was launched, false otherwise.
     */
@@ -743,7 +909,8 @@ public class GestureLauncherService extends SystemService {
                Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
    }

    private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
    private final BroadcastReceiver mUserReceiver =
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
@@ -752,17 +919,24 @@ public class GestureLauncherService extends SystemService {
                        registerContentObservers();
                        updateCameraRegistered();
                        updateCameraDoubleTapPowerEnabled();
                        if (launchWalletOptionOnPowerDoubleTap()) {
                            updateWalletDoubleTapPowerEnabled();
                        }
                        updateEmergencyGestureEnabled();
                        updateEmergencyGesturePowerButtonCooldownPeriodMs();
                    }
                }
            };

    private final ContentObserver mSettingObserver = new ContentObserver(new Handler()) {
    private final ContentObserver mSettingObserver =
            new ContentObserver(new Handler()) {
                public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
                    if (userId == mUserId) {
                        updateCameraRegistered();
                        updateCameraDoubleTapPowerEnabled();
                        if (launchWalletOptionOnPowerDoubleTap()) {
                            updateWalletDoubleTapPowerEnabled();
                        }
                        updateEmergencyGestureEnabled();
                        updateEmergencyGesturePowerButtonCooldownPeriodMs();
                    }
+3 −3
Original line number Diff line number Diff line
@@ -91,7 +91,7 @@ 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.GestureLauncherService.DOUBLE_POWER_TAP_COUNT_THRESHOLD;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
@@ -4150,7 +4150,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

        // Intercept keys (don't send to app) for 3x, 4x, 5x gestures)
        if (mPowerButtonConsecutiveTaps > CAMERA_POWER_TAP_COUNT_THRESHOLD) {
        if (mPowerButtonConsecutiveTaps > DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
            setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, event.getDownTime());
            return true;
        }
@@ -5952,7 +5952,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

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

    // The camera gesture will be detected by GestureLauncherService.
+423 −74

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -278,7 +278,7 @@ class TestPhoneWindowManager {
        mHandler = new Handler(mTestLooper.getLooper());
        mContext = mockingDetails(context).isSpy() ? context : spy(context);
        mGestureLauncherService = spy(new GestureLauncherService(mContext, mMetricsLogger,
                mUiEventLogger));
                mQuickAccessWalletClient, mUiEventLogger));
        setUp(supportSettingsUpdate);
        mTestLooper.dispatchAll();
    }