Revert^2 "Launch wallet app on double tap option."

bf5db7350ead6807896e0bee85d9ef264435c4d0
Bug: 382133936
Test: android.platform.test.scenario.sysui.power.PowerMenuTest#testPower_verifySystemPowerMenuAppears

Change-Id: I1aebd5ab6d4da314a9bd043deaf131b5fae62d3e
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 19bc8e3..d84a892 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -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.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.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 @@
      * 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 @@
     @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 @@
      */
     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 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 @@
                     "GestureLauncherService");
             updateCameraRegistered();
             updateCameraDoubleTapPowerEnabled();
+            if (launchWalletOptionOnPowerDoubleTap()) {
+                updateWalletDoubleTapPowerEnabled();
+            }
             updateEmergencyGestureEnabled();
             updateEmergencyGesturePowerButtonCooldownPeriodMs();
 
@@ -295,6 +328,14 @@
     }
 
     @VisibleForTesting
+    void updateWalletDoubleTapPowerEnabled() {
+        boolean enabled = isWalletDoubleTapPowerSettingEnabled(mContext, mUserId);
+        synchronized (this) {
+            mWalletDoubleTapPowerEnabled = enabled;
+        }
+    }
+
+    @VisibleForTesting
     void updateEmergencyGestureEnabled() {
         boolean enabled = isEmergencyGestureSettingEnabled(mContext, mUserId);
         synchronized (this) {
@@ -421,10 +462,33 @@
                         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())
-                && (Settings.Secure.getIntForUser(context.getContentResolver(),
-                        Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
+        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) {
@@ -444,6 +508,28 @@
                 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 @@
      * Whether GestureLauncherService should be enabled according to system properties.
      */
     public static boolean isGestureLauncherEnabled(Resources resources) {
-        return isCameraLaunchEnabled(resources)
-                || isCameraDoubleTapPowerEnabled(resources)
-                || isCameraLiftTriggerEnabled(resources)
-                || isEmergencyGestureEnabled(resources);
+        boolean res =
+                isCameraLaunchEnabled(resources)
+                        || isCameraLiftTriggerEnabled(resources)
+                        || isEmergencyGestureEnabled(resources);
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            res |= isDoubleTapConfigEnabled(resources);
+        } else {
+            res |= isCameraDoubleTapPowerEnabled(resources);
+        }
+        return res;
     }
 
     /**
@@ -530,7 +622,7 @@
                 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 @@
             return false;
         }
         boolean launchCamera = false;
+        boolean launchWallet = false;
         boolean launchEmergencyGesture = false;
         boolean intercept = false;
         long powerTapInterval;
@@ -584,7 +677,7 @@
                 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 @@
                 }
             }
             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 @@
                         (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 @@
                 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,31 +909,39 @@
                 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
     }
 
-    private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
-                mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
-                registerContentObservers();
-                updateCameraRegistered();
-                updateCameraDoubleTapPowerEnabled();
-                updateEmergencyGestureEnabled();
-                updateEmergencyGesturePowerButtonCooldownPeriodMs();
-            }
-        }
-    };
+    private final BroadcastReceiver mUserReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
+                        mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                        mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
+                        registerContentObservers();
+                        updateCameraRegistered();
+                        updateCameraDoubleTapPowerEnabled();
+                        if (launchWalletOptionOnPowerDoubleTap()) {
+                            updateWalletDoubleTapPowerEnabled();
+                        }
+                        updateEmergencyGestureEnabled();
+                        updateEmergencyGesturePowerButtonCooldownPeriodMs();
+                    }
+                }
+            };
 
-    private final ContentObserver mSettingObserver = new ContentObserver(new Handler()) {
-        public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
-            if (userId == mUserId) {
-                updateCameraRegistered();
-                updateCameraDoubleTapPowerEnabled();
-                updateEmergencyGestureEnabled();
-                updateEmergencyGesturePowerButtonCooldownPeriodMs();
-            }
-        }
-    };
+    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();
+                    }
+                }
+            };
 
     private final class GestureEventListener implements SensorEventListener {
         @Override
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3fb371c..f8a9a9f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -91,7 +91,7 @@
 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 @@
         }
 
         // 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;
         }
@@ -5954,7 +5954,7 @@
         }
 
         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.
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 2349def..9850cb0 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -16,7 +16,12 @@
 
 package com.android.server;
 
-import static com.android.server.GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP;
+import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
+
+import static com.android.server.GestureLauncherService.LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
+import static com.android.server.GestureLauncherService.LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
+import static com.android.server.GestureLauncherService.POWER_DOUBLE_TAP_MAX_TIME_MS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -24,19 +29,27 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.PendingIntent;
 import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
 import android.telecom.TelecomManager;
 import android.test.mock.MockContentResolver;
 import android.testing.TestableLooper;
@@ -55,6 +68,7 @@
 
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -62,6 +76,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Unit tests for {@link GestureLauncherService}.
@@ -90,9 +106,32 @@
     private @Mock TelecomManager mTelecomManager;
     private @Mock MetricsLogger mMetricsLogger;
     @Mock private UiEventLogger mUiEventLogger;
+    @Mock private QuickAccessWalletClient mQuickAccessWalletClient;
     private MockContentResolver mContentResolver;
     private GestureLauncherService mGestureLauncherService;
 
+    private Context mInstrumentationContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private static final String LAUNCH_TEST_WALLET_ACTION = "LAUNCH_TEST_WALLET_ACTION";
+    private static final String LAUNCH_FALLBACK_ACTION = "LAUNCH_FALLBACK_ACTION";
+    private PendingIntent mGesturePendingIntent =
+            PendingIntent.getBroadcast(
+                    mInstrumentationContext,
+                    0,
+                    new Intent(LAUNCH_TEST_WALLET_ACTION),
+                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
+
+    private PendingIntent mFallbackPendingIntent =
+            PendingIntent.getBroadcast(
+                    mInstrumentationContext,
+                    0,
+                    new Intent(LAUNCH_FALLBACK_ACTION),
+                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @BeforeClass
     public static void oneTimeInitialization() {
         if (Looper.myLooper() == null) {
@@ -115,9 +154,49 @@
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
         when(mTelecomManager.createLaunchEmergencyDialerIntent(null)).thenReturn(new Intent());
+        when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
 
-        mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger,
-                mUiEventLogger);
+        mGestureLauncherService =
+                new GestureLauncherService(
+                        mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger);
+
+        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+    }
+
+    private WalletLaunchedReceiver registerWalletLaunchedReceiver(String action) {
+        IntentFilter filter = new IntentFilter(action);
+        WalletLaunchedReceiver receiver = new WalletLaunchedReceiver();
+        mInstrumentationContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
+        return receiver;
+    }
+
+    /**
+     * A simple {@link BroadcastReceiver} implementation that counts down a {@link CountDownLatch}
+     * when a matching message is received
+     */
+    private static final class WalletLaunchedReceiver extends BroadcastReceiver {
+        private static final int TIMEOUT_SECONDS = 3;
+
+        private final CountDownLatch mLatch;
+
+        WalletLaunchedReceiver() {
+            mLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mLatch.countDown();
+            context.unregisterReceiver(this);
+        }
+
+        Boolean waitUntilShown() {
+            try {
+                return mLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
     }
 
     @Test
@@ -134,37 +213,123 @@
 
     @Test
     public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() {
-        withCameraDoubleTapPowerEnableConfigValue(false);
-        withCameraDoubleTapPowerDisableSettingValue(1);
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            withDoubleTapPowerEnabledConfigValue(false);
+            withDoubleTapPowerGestureEnableSettingValue(false);
+            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        } else {
+            withCameraDoubleTapPowerEnableConfigValue(false);
+            withCameraDoubleTapPowerDisableSettingValue(1);
+        }
         assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
     }
 
     @Test
     public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() {
-        withCameraDoubleTapPowerEnableConfigValue(false);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
-                mContext, FAKE_USER_ID));
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            withDoubleTapPowerEnabledConfigValue(false);
+            withDoubleTapPowerGestureEnableSettingValue(true);
+            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+            assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                    mContext, FAKE_USER_ID));
+        } else {
+            withCameraDoubleTapPowerEnableConfigValue(false);
+            withCameraDoubleTapPowerDisableSettingValue(0);
+            assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                    mContext, FAKE_USER_ID));
+        }
     }
 
     @Test
     public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(1);
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            withDoubleTapPowerEnabledConfigValue(true);
+            withDoubleTapPowerGestureEnableSettingValue(false);
+            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        } else {
+            withCameraDoubleTapPowerEnableConfigValue(true);
+            withCameraDoubleTapPowerDisableSettingValue(1);
+        }
         assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
     }
 
     @Test
     public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            withDoubleTapPowerEnabledConfigValue(true);
+            withDoubleTapPowerGestureEnableSettingValue(true);
+            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        } else {
+            withCameraDoubleTapPowerEnableConfigValue(true);
+            withCameraDoubleTapPowerDisableSettingValue(0);
+        }
         assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() {
+        withDoubleTapPowerEnabledConfigValue(true);
+        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+        assertFalse(
+                mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsWalletDoubleTapPowerSettingEnabled() {
+        withDoubleTapPowerEnabledConfigValue(true);
+        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+        assertTrue(
+                mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() {
+        withDoubleTapPowerEnabledConfigValue(false);
+        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+        assertTrue(
+                mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() {
+        withDoubleTapPowerEnabledConfigValue(true);
+        withDoubleTapPowerGestureEnableSettingValue(false);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+        assertFalse(
+                mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() {
+        withDoubleTapPowerEnabledConfigValue(true);
+        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+        assertFalse(
+                mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
     public void testIsEmergencyGestureSettingEnabled_settingDisabled() {
         withEmergencyGestureEnabledConfigValue(true);
         withEmergencyGestureEnabledSettingValue(false);
@@ -245,12 +410,9 @@
 
     @Test
     public void testInterceptPowerKeyDown_firstPowerDownCameraPowerGestureOnInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        enableCameraGesture();
 
-        long eventTime = INITIAL_EVENT_TIME_MILLIS +
-                CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        long eventTime = INITIAL_EVENT_TIME_MILLIS + POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
         boolean interactive = true;
@@ -284,8 +446,12 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(false);
-        withCameraDoubleTapPowerDisableSettingValue(1);
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            withDoubleTapPowerGestureEnableSettingValue(false);
+        } else {
+            withCameraDoubleTapPowerEnableConfigValue(false);
+            withCameraDoubleTapPowerDisableSettingValue(1);
+        }
         mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -298,7 +464,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -329,8 +495,12 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(false);
-        withCameraDoubleTapPowerDisableSettingValue(1);
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            withDoubleTapPowerGestureEnableSettingValue(false);
+        } else {
+            withCameraDoubleTapPowerEnableConfigValue(false);
+            withCameraDoubleTapPowerDisableSettingValue(1);
+        }
         mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -343,7 +513,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -422,10 +592,7 @@
     @Test
     public void
     testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupComplete() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
-        withUserSetupCompleteValue(true);
+        enableCameraGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -437,7 +604,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -470,15 +637,145 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void
+            testInterceptPowerKeyDown_fiveInboundPresses_walletAndEmergencyEnabled_bothLaunch() {
+        WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+        setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
+        enableEmergencyGesture();
+        enableWalletGesture();
+
+        // First event
+        long eventTime = INITIAL_EVENT_TIME_MILLIS;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        eventTime += interval;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
+
+        assertTrue(receiver.waitUntilShown());
+
+        // 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 trigger the emergency flow
+        eventTime += interval;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
+
+        verify(mUiEventLogger, times(1))
+                .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
+        verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testInterceptPowerKeyDown_intervalInBoundsWalletPowerGesture() {
+        WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+        setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
+        enableWalletGesture();
+        enableEmergencyGesture();
+
+        long eventTime = INITIAL_EVENT_TIME_MILLIS;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        eventTime += interval;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
+        assertTrue(receiver.waitUntilShown());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testInterceptPowerKeyDown_walletGestureOn_quickAccessWalletServiceUnavailable() {
+        when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
+        WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+        setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
+        enableWalletGesture();
+
+        // First event
+        long eventTime = INITIAL_EVENT_TIME_MILLIS;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        eventTime += interval;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
+
+        assertFalse(receiver.waitUntilShown());
+    }
+
+    @Test
+    public void testInterceptPowerKeyDown_walletGestureOn_userSetupIncomplete() {
+        WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+        setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
+        enableWalletGesture();
+        withUserSetupCompleteValue(false);
+
+        // First event
+        long eventTime = INITIAL_EVENT_TIME_MILLIS;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        eventTime += interval;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+        assertFalse(receiver.waitUntilShown());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testInterceptPowerKeyDown_walletPowerGesture_nullPendingIntent() {
+        WalletLaunchedReceiver gestureReceiver =
+                registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+        setUpGetGestureTargetActivityPendingIntent(null);
+        WalletLaunchedReceiver fallbackReceiver =
+                registerWalletLaunchedReceiver(LAUNCH_FALLBACK_ACTION);
+        setUpWalletFallbackPendingIntent(mFallbackPendingIntent);
+        enableWalletGesture();
+        enableEmergencyGesture();
+
+        // First event
+        long eventTime = INITIAL_EVENT_TIME_MILLIS;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        eventTime += interval;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
+
+        assertFalse(gestureReceiver.waitUntilShown());
+        assertTrue(fallbackReceiver.waitUntilShown());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testInterceptPowerKeyDown_walletPowerGesture_intervalOutOfBounds() {
+        WalletLaunchedReceiver gestureReceiver =
+                registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+        setUpGetGestureTargetActivityPendingIntent(null);
+        WalletLaunchedReceiver fallbackReceiver =
+                registerWalletLaunchedReceiver(LAUNCH_FALLBACK_ACTION);
+        setUpWalletFallbackPendingIntent(mFallbackPendingIntent);
+        enableWalletGesture();
+        enableEmergencyGesture();
+
+        // First event
+        long eventTime = INITIAL_EVENT_TIME_MILLIS;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+        eventTime += interval;
+        sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+        assertFalse(gestureReceiver.waitUntilShown());
+        assertFalse(fallbackReceiver.waitUntilShown());
+    }
+
+    @Test
     public void
             testInterceptPowerKeyDown_fiveInboundPresses_cameraAndEmergencyEnabled_bothLaunch() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        withEmergencyGestureEnabledConfigValue(true);
-        withEmergencyGestureEnabledSettingValue(true);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
-        mGestureLauncherService.updateEmergencyGestureEnabled();
-        withUserSetupCompleteValue(true);
+        enableCameraGesture();
+        enableEmergencyGesture();
 
         // First button press does nothing
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -491,7 +788,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
 
         // 2nd button triggers camera
         eventTime += interval;
@@ -580,7 +877,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         // 3 more button presses which should not trigger any gesture (camera gesture disabled)
         for (int i = 0; i < 3; i++) {
             eventTime += interval;
@@ -634,7 +931,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         // 3 more button presses which should not trigger any gesture, but intercepts action.
         for (int i = 0; i < 3; i++) {
             eventTime += interval;
@@ -737,7 +1034,7 @@
                     interactive, outLaunched);
             assertTrue(intercepted);
             assertFalse(outLaunched.value);
-            interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+            interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
             eventTime += interval;
         }
     }
@@ -765,7 +1062,7 @@
                     interactive, outLaunched);
             assertTrue(intercepted);
             assertFalse(outLaunched.value);
-            interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+            interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
             eventTime += interval;
         }
     }
@@ -916,7 +1213,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
@@ -947,9 +1244,7 @@
     @Test
     public void
     testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupIncomplete() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        enableCameraGesture();
         withUserSetupCompleteValue(false);
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -962,7 +1257,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -995,9 +1290,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        enableCameraGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1009,7 +1302,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -1042,9 +1335,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        enableCameraGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1087,8 +1378,12 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(false);
-        withCameraDoubleTapPowerDisableSettingValue(1);
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            withDoubleTapPowerGestureEnableSettingValue(false);
+        } else {
+            withCameraDoubleTapPowerEnableConfigValue(false);
+            withCameraDoubleTapPowerDisableSettingValue(1);
+        }
         mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1101,7 +1396,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -1146,7 +1441,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -1223,10 +1518,7 @@
     @Test
     public void
     testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupComplete() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
-        withUserSetupCompleteValue(true);
+        enableCameraGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1238,7 +1530,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -1272,9 +1564,7 @@
     @Test
     public void
     testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupIncomplete() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        enableCameraGesture();
         withUserSetupCompleteValue(false);
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1287,7 +1577,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -1332,7 +1622,7 @@
         assertFalse(intercepted);
         assertFalse(outLaunched.value);
 
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
         eventTime += interval;
         keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
                 IGNORED_REPEAT);
@@ -1365,9 +1655,7 @@
 
     @Test
     public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnNotInteractive() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
-        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+        enableCameraGesture();
 
         long eventTime = INITIAL_EVENT_TIME_MILLIS;
         KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1423,7 +1711,7 @@
         sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
 
         //Second event; call processPowerKeyDown without calling interceptPowerKeyDown
-        final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+        final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
         eventTime += interval;
         KeyEvent keyEvent =
                 new KeyEvent(
@@ -1455,7 +1743,7 @@
      * @return last event time.
      */
     private long triggerEmergencyGesture() {
-        return triggerEmergencyGesture(CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1);
+        return triggerEmergencyGesture(POWER_DOUBLE_TAP_MAX_TIME_MS - 1);
     }
 
     /**
@@ -1514,6 +1802,27 @@
                 .thenReturn(enableConfigValue);
     }
 
+    private void withDoubleTapPowerEnabledConfigValue(boolean enable) {
+        when(mResources.getBoolean(com.android.internal.R.bool.config_doubleTapPowerGestureEnabled))
+                .thenReturn(enable);
+    }
+
+    private void withDoubleTapPowerGestureEnableSettingValue(boolean enable) {
+        Settings.Secure.putIntForUser(
+                mContentResolver,
+                Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
+                enable ? 1 : 0,
+                UserHandle.USER_CURRENT);
+    }
+
+    private void withDefaultDoubleTapPowerGestureAction(int action) {
+        Settings.Secure.putIntForUser(
+                mContentResolver,
+                Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
+                action,
+                UserHandle.USER_CURRENT);
+    }
+
     private void withEmergencyGestureEnabledConfigValue(boolean enableConfigValue) {
         when(mResources.getBoolean(
                 com.android.internal.R.bool.config_emergencyGestureEnabled))
@@ -1552,6 +1861,40 @@
                 UserHandle.USER_CURRENT);
     }
 
+    private void setUpGetGestureTargetActivityPendingIntent(PendingIntent pendingIntent) {
+        doAnswer(
+                        invocation -> {
+                            QuickAccessWalletClient.GesturePendingIntentCallback callback =
+                                    (QuickAccessWalletClient.GesturePendingIntentCallback)
+                                            invocation.getArguments()[1];
+                            callback.onGesturePendingIntentRetrieved(pendingIntent);
+                            return null;
+                        })
+                .when(mQuickAccessWalletClient)
+                .getGestureTargetActivityPendingIntent(any(), any());
+    }
+
+    private void setUpWalletFallbackPendingIntent(PendingIntent pendingIntent) {
+        doAnswer(
+                        invocation -> {
+                            QuickAccessWalletClient.WalletPendingIntentCallback callback =
+                                    (QuickAccessWalletClient.WalletPendingIntentCallback)
+                                            invocation.getArguments()[1];
+                            callback.onWalletPendingIntentRetrieved(pendingIntent);
+                            return null;
+                        })
+                .when(mQuickAccessWalletClient)
+                .getWalletPendingIntent(any(), any());
+    }
+
+    private void enableWalletGesture() {
+        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureEnableSettingValue(true);
+        withDoubleTapPowerEnabledConfigValue(true);
+
+        mGestureLauncherService.updateWalletDoubleTapPowerEnabled();
+        withUserSetupCompleteValue(true);
+    }
 
     private void enableEmergencyGesture() {
         withEmergencyGestureEnabledConfigValue(true);
@@ -1561,8 +1904,14 @@
     }
 
     private void enableCameraGesture() {
-        withCameraDoubleTapPowerEnableConfigValue(true);
-        withCameraDoubleTapPowerDisableSettingValue(0);
+        if (launchWalletOptionOnPowerDoubleTap()) {
+            withDoubleTapPowerEnabledConfigValue(true);
+            withDoubleTapPowerGestureEnableSettingValue(true);
+            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        } else {
+            withCameraDoubleTapPowerEnableConfigValue(true);
+            withCameraDoubleTapPowerDisableSettingValue(0);
+        }
         mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
         withUserSetupCompleteValue(true);
     }
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 18ecfa1..f06b45e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -278,7 +278,7 @@
         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();
     }