summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/policy/KeyInterceptionInfo.java4
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java309
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java232
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java560
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java154
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java22
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java105
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt3
9 files changed, 1259 insertions, 135 deletions
diff --git a/core/java/com/android/internal/policy/KeyInterceptionInfo.java b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
index b20f6d225b69..fed8fe3b4cc0 100644
--- a/core/java/com/android/internal/policy/KeyInterceptionInfo.java
+++ b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
@@ -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;
}
}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index ccc44a41759b..d84a892e4f54 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -16,9 +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;
@@ -33,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;
@@ -41,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;
@@ -70,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;
/**
@@ -100,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.
- */
- private 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();
@@ -158,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
*/
@@ -204,14 +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
- GestureLauncherService(Context context, MetricsLogger metricsLogger,
+ public GestureLauncherService(
+ Context context,
+ MetricsLogger metricsLogger,
+ QuickAccessWalletClient quickAccessWalletClient,
UiEventLogger uiEventLogger) {
super(context);
mContext = context;
+ mQuickAccessWalletClient = quickAccessWalletClient;
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
}
@@ -237,6 +270,9 @@ public class GestureLauncherService extends SystemService {
"GestureLauncherService");
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ updateWalletDoubleTapPowerEnabled();
+ }
updateEmergencyGestureEnabled();
updateEmergencyGesturePowerButtonCooldownPeriodMs();
@@ -292,6 +328,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);
synchronized (this) {
@@ -418,10 +462,33 @@ 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())
- && (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) {
@@ -441,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
@@ -494,10 +583,56 @@ 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)
- || isCameraLiftTriggerEnabled(resources)
- || isEmergencyGestureEnabled(resources);
+ boolean res =
+ isCameraLaunchEnabled(resources)
+ || isCameraLiftTriggerEnabled(resources)
+ || isEmergencyGestureEnabled(resources);
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ res |= isDoubleTapConfigEnabled(resources);
+ } else {
+ res |= isCameraDoubleTapPowerEnabled(resources);
+ }
+ return res;
+ }
+
+ /**
+ * 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 >= 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++;
+ }
+ }
}
/**
@@ -507,8 +642,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) {
@@ -530,6 +665,7 @@ public class GestureLauncherService extends SystemService {
return false;
}
boolean launchCamera = false;
+ boolean launchWallet = false;
boolean launchEmergencyGesture = false;
boolean intercept = false;
long powerTapInterval;
@@ -541,12 +677,12 @@ 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;
mPowerButtonSlowConsecutiveTaps++;
- } else {
+ } else if (!overridePowerKeyBehaviorInFocusedWindow() || powerTapInterval > 0) {
// Fast consecutive tap
mPowerButtonConsecutiveTaps++;
mPowerButtonSlowConsecutiveTaps++;
@@ -586,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) {
@@ -608,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();
@@ -623,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.
*/
@@ -700,31 +909,39 @@ public class GestureLauncherService extends SystemService {
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 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 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();
+ 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 7c4d4253157e..e39906bdc366 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -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.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.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;
@@ -432,6 +434,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
"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
* where the window manager is calling in with its own lock held.)
@@ -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,7 +2712,19 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (mShouldEarlyShortPressOnPower) {
return;
}
- powerPress(downTime, 1 /*count*/, displayId);
+ // 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
@@ -2700,7 +2755,17 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
void onMultiPress(long downTime, int count, int displayId) {
- powerPress(downTime, count, displayId);
+ if (overridePowerKeyBehaviorInFocusedWindow()) {
+ mDeferredKeyActionExecutor.cancelQueuedAction(KEYCODE_POWER);
+ mDeferredKeyActionExecutor.queueKeyAction(
+ KEYCODE_POWER,
+ downTime,
+ () -> {
+ powerPress(downTime, count, displayId);
+ });
+ } else {
+ powerPress(downTime, count, displayId);
+ }
}
@Override
@@ -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 > DOUBLE_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));
- // Any activity on the power button stops the accessibility shortcut
- result &= ~ACTION_PASS_TO_USER;
+ 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 == DOUBLE_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) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index cebe790bb1b9..447d443282bd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -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;
}
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 8024915692aa..9850cb09ae2b 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.junit.Assert.assertTrue;
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 com.android.server.statusbar.StatusBarManagerInternal;
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.Mock;
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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
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, mQuickAccessWalletClient, mUiEventLogger);
+
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ }
- mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger,
- mUiEventLogger);
+ 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 @@ public class GestureLauncherServiceTest {
@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 @@ public class GestureLauncherServiceTest {
@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 @@ public class GestureLauncherServiceTest {
@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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
@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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
@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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
}
@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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupIncomplete() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
withUserSetupCompleteValue(false);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -962,7 +1257,7 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
@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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
@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 @@ public class GestureLauncherServiceTest {
@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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
@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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupIncomplete() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
withUserSetupCompleteValue(false);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1287,7 +1577,7 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
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 @@ public class GestureLauncherServiceTest {
@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,
@@ -1409,12 +1697,53 @@ public class GestureLauncherServiceTest {
}
/**
+ * 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 = 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.
*
* @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);
}
/**
@@ -1473,6 +1802,27 @@ public class GestureLauncherServiceTest {
.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))
@@ -1510,4 +1860,72 @@ public class GestureLauncherServiceTest {
userSetupCompleteValue,
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);
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateEmergencyGestureEnabled();
+ withUserSetupCompleteValue(true);
+ }
+
+ private void enableCameraGesture() {
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerEnabledConfigValue(true);
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ } else {
+ 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);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index 05a1482b9be6..be516e9553c0 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -18,15 +18,22 @@ package com.android.server.policy;
import static android.view.KeyEvent.KEYCODE_POWER;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
+import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
+import static com.android.server.policy.PhoneWindowManager.POWER_MULTI_PRESS_TIMEOUT_MILLIS;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_DREAM_OR_SLEEP;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_GO_TO_SLEEP;
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
/**
@@ -39,8 +46,12 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase {
@Before
public void setUp() {
setUpPhoneWindowManager();
+ mPhoneWindowManager.overrideStatusBarManagerInternal();
}
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
/**
* Power single press to turn screen on/off.
*/
@@ -50,6 +61,8 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase {
sendKey(KEYCODE_POWER);
mPhoneWindowManager.assertPowerSleep();
+ mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
+
// turn screen on when begin from non-interactive.
mPhoneWindowManager.overrideDisplayState(Display.STATE_OFF);
sendKey(KEYCODE_POWER);
@@ -90,7 +103,7 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase {
mPhoneWindowManager.overrideCanStartDreaming(false);
sendKey(KEYCODE_POWER);
sendKey(KEYCODE_POWER);
- mPhoneWindowManager.assertCameraLaunch();
+ mPhoneWindowManager.assertDoublePowerLaunch();
mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
}
@@ -101,7 +114,7 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase {
public void testPowerDoublePress() {
sendKey(KEYCODE_POWER);
sendKey(KEYCODE_POWER);
- mPhoneWindowManager.assertCameraLaunch();
+ mPhoneWindowManager.assertDoublePowerLaunch();
}
/**
@@ -114,6 +127,8 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase {
sendKey(KEYCODE_POWER, true);
mPhoneWindowManager.assertSearchManagerLaunchAssist();
+ mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
+
// Show global actions.
mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_GLOBAL_ACTIONS);
sendKey(KEYCODE_POWER, true);
@@ -141,4 +156,139 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase {
sendKey(KEYCODE_POWER);
mPhoneWindowManager.assertNoPowerSleep();
}
+
+ /**
+ * Double press of power when the window handles the power key events. The
+ * system double power gesture launch should not be performed.
+ */
+ @Test
+ @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testPowerDoublePress_windowHasOverridePermissionAndKeysHandled() {
+ mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
+ setDispatchedKeyHandler(keyEvent -> true);
+
+ sendKey(KEYCODE_POWER);
+ sendKey(KEYCODE_POWER);
+
+ mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
+
+ mPhoneWindowManager.assertNoDoublePowerLaunch();
+ }
+
+ /**
+ * Double press of power when the window doesn't handle the power key events.
+ * The system default gesture launch should be performed and the app should receive both events.
+ */
+ @Test
+ @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testPowerDoublePress_windowHasOverridePermissionAndKeysUnHandled() {
+ mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
+ setDispatchedKeyHandler(keyEvent -> false);
+
+ sendKey(KEYCODE_POWER);
+ sendKey(KEYCODE_POWER);
+
+ mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
+ mPhoneWindowManager.assertDoublePowerLaunch();
+ assertEquals(getDownKeysDispatched(), 2);
+ assertEquals(getUpKeysDispatched(), 2);
+ }
+
+ /**
+ * Triple press of power when the window handles the power key double press gesture.
+ * The system default gesture launch should not be performed, and the app only receives the
+ * first two presses.
+ */
+ @Test
+ @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testPowerTriplePress_windowHasOverridePermissionAndKeysHandled() {
+ mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
+ setDispatchedKeyHandler(keyEvent -> true);
+
+ sendKey(KEYCODE_POWER);
+ sendKey(KEYCODE_POWER);
+ sendKey(KEYCODE_POWER);
+
+ mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
+ mPhoneWindowManager.assertNoDoublePowerLaunch();
+ assertEquals(getDownKeysDispatched(), 2);
+ assertEquals(getUpKeysDispatched(), 2);
+ }
+
+ /**
+ * Tests a single press, followed by a double press when the window can handle the power key.
+ * The app should receive all 3 events.
+ */
+ @Test
+ @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testPowerTriplePressWithDelay_windowHasOverridePermissionAndKeysHandled() {
+ mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
+ setDispatchedKeyHandler(keyEvent -> true);
+
+ sendKey(KEYCODE_POWER);
+ mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
+ sendKey(KEYCODE_POWER);
+ sendKey(KEYCODE_POWER);
+
+ mPhoneWindowManager.assertNoDoublePowerLaunch();
+ assertEquals(getDownKeysDispatched(), 3);
+ assertEquals(getUpKeysDispatched(), 3);
+ }
+
+ /**
+ * Tests single press when window doesn't handle the power key. Phone should go to sleep.
+ */
+ @Test
+ @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testPowerSinglePress_windowHasOverridePermissionAndKeyUnhandledByApp() {
+ mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
+ setDispatchedKeyHandler(keyEvent -> false);
+ mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
+
+ sendKey(KEYCODE_POWER);
+
+ mPhoneWindowManager.assertPowerSleep();
+ }
+
+ /**
+ * Tests single press when the window handles the power key. Phone should go to sleep after a
+ * delay of {POWER_MULTI_PRESS_TIMEOUT_MILLIS}
+ */
+ @Test
+ @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testPowerSinglePress_windowHasOverridePermissionAndKeyHandledByApp() {
+ mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
+ setDispatchedKeyHandler(keyEvent -> true);
+ mPhoneWindowManager.overrideDisplayState(Display.STATE_ON);
+ mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
+
+ sendKey(KEYCODE_POWER);
+
+ mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
+
+ mPhoneWindowManager.assertPowerSleep();
+ }
+
+
+ /**
+ * Tests 5x press when the window handles the power key. Emergency gesture should still be
+ * launched.
+ */
+ @Test
+ @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testPowerFiveTimesPress_windowHasOverridePermissionAndKeyHandledByApp() {
+ mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
+ setDispatchedKeyHandler(keyEvent -> true);
+ mPhoneWindowManager.overrideDisplayState(Display.STATE_ON);
+ mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
+
+ for (int i = 0; i < 5; ++i) {
+ sendKey(KEYCODE_POWER);
+ mPhoneWindowManager.moveTimeForward(100);
+ }
+
+ mPhoneWindowManager.assertEmergencyLaunch();
+ assertEquals(getDownKeysDispatched(), 2);
+ assertEquals(getUpKeysDispatched(), 2);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 9e47a008592c..591950176444 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -85,7 +85,11 @@ class ShortcutKeyTestBase {
private Resources mResources;
private PackageManager mPackageManager;
TestPhoneWindowManager mPhoneWindowManager;
- DispatchedKeyHandler mDispatchedKeyHandler = event -> false;
+
+ DispatchedKeyHandler mDispatchedKeyHandler;
+ private int mDownKeysDispatched;
+ private int mUpKeysDispatched;
+
Context mContext;
/** Modifier key to meta state */
@@ -116,6 +120,9 @@ class ShortcutKeyTestBase {
XmlResourceParser testBookmarks = mResources.getXml(
com.android.frameworks.wmtests.R.xml.bookmarks);
doReturn(testBookmarks).when(mResources).getXml(com.android.internal.R.xml.bookmarks);
+ mDispatchedKeyHandler = event -> false;
+ mDownKeysDispatched = 0;
+ mUpKeysDispatched = 0;
try {
// Keep packageName / className in sync with
@@ -278,6 +285,14 @@ class ShortcutKeyTestBase {
doReturn(expectedBehavior).when(mResources).getInteger(eq(resId));
}
+ int getDownKeysDispatched() {
+ return mDownKeysDispatched;
+ }
+
+ int getUpKeysDispatched() {
+ return mUpKeysDispatched;
+ }
+
private void interceptKey(KeyEvent keyEvent) {
int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent);
if ((actions & ACTION_PASS_TO_USER) != 0) {
@@ -285,6 +300,11 @@ class ShortcutKeyTestBase {
if (!mDispatchedKeyHandler.onKeyDispatched(keyEvent)) {
mPhoneWindowManager.interceptUnhandledKey(keyEvent);
}
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ ++mDownKeysDispatched;
+ } else {
+ ++mUpKeysDispatched;
+ }
}
}
mPhoneWindowManager.dispatchAllPendingEvents();
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 9db76d47fed7..f06b45e94f77 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -22,6 +22,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.STATE_ON;
import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
+import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
@@ -45,10 +46,14 @@ import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_SHUT
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.timeout;
@@ -85,7 +90,9 @@ import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.telecom.TelecomManager;
+import android.util.MutableBoolean;
import android.view.Display;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
@@ -95,9 +102,12 @@ import android.view.autofill.AutofillManagerInternal;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.GestureLauncherService;
import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.UserManagerInternal;
@@ -120,6 +130,7 @@ import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.util.List;
import java.util.function.Supplier;
@@ -132,6 +143,8 @@ class TestPhoneWindowManager {
private PhoneWindowManager mPhoneWindowManager;
private Context mContext;
+ private GestureLauncherService mGestureLauncherService;
+
@Mock private WindowManagerInternal mWindowManagerInternal;
@Mock private ActivityManagerInternal mActivityManagerInternal;
@@ -163,7 +176,9 @@ class TestPhoneWindowManager {
@Mock private DisplayRotation mDisplayRotation;
@Mock private DisplayPolicy mDisplayPolicy;
@Mock private WindowManagerPolicy.ScreenOnListener mScreenOnListener;
- @Mock private GestureLauncherService mGestureLauncherService;
+ @Mock private QuickAccessWalletClient mQuickAccessWalletClient;
+ @Mock private MetricsLogger mMetricsLogger;
+ @Mock private UiEventLogger mUiEventLogger;
@Mock private GlobalActions mGlobalActions;
@Mock private AccessibilityShortcutController mAccessibilityShortcutController;
@@ -192,6 +207,8 @@ class TestPhoneWindowManager {
private int mKeyEventPolicyFlags = FLAG_INTERACTIVE;
+ private int mProcessPowerKeyDownCount = 0;
+
private class TestTalkbackShortcutController extends TalkbackShortcutController {
TestTalkbackShortcutController(Context context) {
super(context);
@@ -260,6 +277,8 @@ class TestPhoneWindowManager {
MockitoAnnotations.initMocks(this);
mHandler = new Handler(mTestLooper.getLooper());
mContext = mockingDetails(context).isSpy() ? context : spy(context);
+ mGestureLauncherService = spy(new GestureLauncherService(mContext, mMetricsLogger,
+ mQuickAccessWalletClient, mUiEventLogger));
setUp(supportSettingsUpdate);
mTestLooper.dispatchAll();
}
@@ -272,6 +291,7 @@ class TestPhoneWindowManager {
mMockitoSession = mockitoSession()
.mockStatic(LocalServices.class, spyStubOnly)
.mockStatic(KeyCharacterMap.class)
+ .mockStatic(GestureLauncherService.class)
.strictness(Strictness.LENIENT)
.startMocking();
@@ -294,6 +314,16 @@ class TestPhoneWindowManager {
() -> LocalServices.getService(eq(PowerManagerInternal.class)));
doReturn(mDisplayManagerInternal).when(
() -> LocalServices.getService(eq(DisplayManagerInternal.class)));
+ doReturn(true).when(
+ () -> GestureLauncherService.isCameraDoubleTapPowerSettingEnabled(any(), anyInt())
+ );
+ doReturn(true).when(
+ () -> GestureLauncherService.isEmergencyGestureSettingEnabled(any(), anyInt())
+ );
+ doReturn(true).when(
+ () -> GestureLauncherService.isGestureLauncherEnabled(any())
+ );
+ mGestureLauncherService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
doReturn(mGestureLauncherService).when(
() -> LocalServices.getService(eq(GestureLauncherService.class)));
doReturn(mUserManagerInternal).when(
@@ -375,7 +405,8 @@ class TestPhoneWindowManager {
doNothing().when(mContext).startActivityAsUser(any(), any());
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
- KeyInterceptionInfo interceptionInfo = new KeyInterceptionInfo(0, 0, null, 0);
+ KeyInterceptionInfo interceptionInfo = new KeyInterceptionInfo(0, 0, null, 0,
+ /* inputFeatureFlags = */ 0);
doReturn(interceptionInfo)
.when(mWindowManagerInternal).getKeyInterceptionInfoFromToken(any());
@@ -393,6 +424,8 @@ class TestPhoneWindowManager {
eq(TEST_BROWSER_ROLE_PACKAGE_NAME));
doReturn(mSmsIntent).when(mPackageManager).getLaunchIntentForPackage(
eq(TEST_SMS_ROLE_PACKAGE_NAME));
+ mProcessPowerKeyDownCount = 0;
+ captureProcessPowerKeyDownCount();
Mockito.reset(mContext);
}
@@ -638,6 +671,12 @@ class TestPhoneWindowManager {
.when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
}
+ void overrideCanWindowOverridePowerKey(boolean granted) {
+ doReturn(granted)
+ .when(mButtonOverridePermissionChecker).canWindowOverridePowerKey(any(), anyInt(),
+ anyInt());
+ }
+
void overrideKeyEventPolicyFlags(int flags) {
mKeyEventPolicyFlags = flags;
}
@@ -713,13 +752,59 @@ class TestPhoneWindowManager {
verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
}
- void assertCameraLaunch() {
+ void assertDoublePowerLaunch() {
+ ArgumentCaptor<MutableBoolean> valueCaptor = ArgumentCaptor.forClass(MutableBoolean.class);
+
mTestLooper.dispatchAll();
- // GestureLauncherService should receive interceptPowerKeyDown twice.
- verify(mGestureLauncherService, times(2))
- .interceptPowerKeyDown(any(), anyBoolean(), any());
+ verify(mGestureLauncherService, atLeast(2))
+ .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
+ verify(mGestureLauncherService, atMost(4))
+ .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
+
+ if (overridePowerKeyBehaviorInFocusedWindow()) {
+ assertTrue(mProcessPowerKeyDownCount >= 2 && mProcessPowerKeyDownCount <= 4);
+ }
+
+ List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
+ .map(mutableBoolean -> mutableBoolean.value)
+ .toList();
+
+ assertTrue(capturedValues.contains(true));
}
+ void assertNoDoublePowerLaunch() {
+ ArgumentCaptor<MutableBoolean> valueCaptor = ArgumentCaptor.forClass(MutableBoolean.class);
+
+ mTestLooper.dispatchAll();
+ verify(mGestureLauncherService, atLeast(0))
+ .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
+
+ List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
+ .map(mutableBoolean -> mutableBoolean.value)
+ .toList();
+
+ assertTrue(capturedValues.stream().noneMatch(value -> value));
+ }
+
+ void assertEmergencyLaunch() {
+ ArgumentCaptor<MutableBoolean> valueCaptor = ArgumentCaptor.forClass(MutableBoolean.class);
+
+ mTestLooper.dispatchAll();
+ verify(mGestureLauncherService, atLeast(1))
+ .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
+
+ if (overridePowerKeyBehaviorInFocusedWindow()) {
+ assertEquals(mProcessPowerKeyDownCount, 5);
+ }
+
+ List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
+ .map(mutableBoolean -> mutableBoolean.value)
+ .toList();
+
+ assertTrue(capturedValues.getLast());
+ }
+
+
void assertSearchManagerLaunchAssist() {
mTestLooper.dispatchAll();
verify(mSearchManager).launchAssist(any());
@@ -929,4 +1014,12 @@ class TestPhoneWindowManager {
verify(mInputManagerInternal)
.handleKeyGestureInKeyGestureController(anyInt(), any(), anyInt(), eq(gestureType));
}
+
+ private void captureProcessPowerKeyDownCount() {
+ doAnswer((Answer<Void>) invocation -> {
+ invocation.callRealMethod();
+ mProcessPowerKeyDownCount++;
+ return null;
+ }).when(mGestureLauncherService).processPowerKeyDown(any());
+ }
}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 43844f6514e8..038c6d7754b5 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -541,7 +541,8 @@ class InputManagerServiceTests {
0
},
"title",
- /* uid = */0
+ /* uid = */0,
+ /* inputFeatureFlags = */ 0
)
whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info)
}