Introduce system shortcut key unit tests
Provide an enviroment that could verify if a key or a combination keys
could be processed properly by the policy and execute the shortcut
behavior.
This includes these tests to for phone device:
CombinationKeyTests - test cases for combination keys
- power + volume down
- power + volume up
- volume down + volume up
PowerKeyGestureTests - test cases for power key gesture
- short press
- double presses
- long press
Bug: 193278327
Test: atest PowerKeyGestureTests CombinationKeyTests
Change-Id: I97424a7acb4a0d02c3bf29343c6fc552da0f7d65
This commit is contained in:
@@ -185,6 +185,7 @@ import android.view.autofill.AutofillManagerInternal;
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.accessibility.AccessibilityShortcutController;
|
||||
import com.android.internal.accessibility.util.AccessibilityUtils;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.AssistUtils;
|
||||
import com.android.internal.inputmethod.SoftInputShowHideReason;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
@@ -1544,9 +1545,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
}
|
||||
|
||||
void showGlobalActionsInternal() {
|
||||
if (mGlobalActions == null) {
|
||||
mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
|
||||
}
|
||||
final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
|
||||
mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
|
||||
// since it took two seconds of long press to bring this up,
|
||||
@@ -1869,11 +1867,45 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
mDefaultDisplayPolicy = mDefaultDisplayRotation.getDisplayPolicy();
|
||||
}
|
||||
|
||||
/** Point of injection for test dependencies. */
|
||||
@VisibleForTesting
|
||||
static class Injector {
|
||||
private final Context mContext;
|
||||
private final WindowManagerFuncs mWindowManagerFuncs;
|
||||
|
||||
Injector(Context context, WindowManagerFuncs funcs) {
|
||||
mContext = context;
|
||||
mWindowManagerFuncs = funcs;
|
||||
}
|
||||
|
||||
Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
WindowManagerFuncs getWindowManagerFuncs() {
|
||||
return mWindowManagerFuncs;
|
||||
}
|
||||
|
||||
AccessibilityShortcutController getAccessibilityShortcutController(
|
||||
Context context, Handler handler, int initialUserId) {
|
||||
return new AccessibilityShortcutController(context, handler, initialUserId);
|
||||
}
|
||||
|
||||
GlobalActions getGlobalActions() {
|
||||
return new GlobalActions(mContext, mWindowManagerFuncs);
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void init(Context context, WindowManagerFuncs windowManagerFuncs) {
|
||||
mContext = context;
|
||||
mWindowManagerFuncs = windowManagerFuncs;
|
||||
public void init(Context context, WindowManagerFuncs funcs) {
|
||||
init(new Injector(context, funcs));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void init(Injector injector) {
|
||||
mContext = injector.getContext();
|
||||
mWindowManagerFuncs = injector.getWindowManagerFuncs();
|
||||
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
|
||||
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
|
||||
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
|
||||
@@ -1888,8 +1920,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
mHasFeatureLeanback = mPackageManager.hasSystemFeature(FEATURE_LEANBACK);
|
||||
mHasFeatureAuto = mPackageManager.hasSystemFeature(FEATURE_AUTOMOTIVE);
|
||||
mHasFeatureHdmiCec = mPackageManager.hasSystemFeature(FEATURE_HDMI_CEC);
|
||||
mAccessibilityShortcutController =
|
||||
new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
|
||||
mAccessibilityShortcutController = injector.getAccessibilityShortcutController(
|
||||
mContext, new Handler(), mCurrentUserId);
|
||||
mGlobalActions = injector.getGlobalActions();
|
||||
mLogger = new MetricsLogger();
|
||||
|
||||
mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal
|
||||
@@ -1904,7 +1937,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
res.getBoolean(com.android.internal.R.bool.config_wakeOnBackKeyPress);
|
||||
|
||||
// Init display burn-in protection
|
||||
boolean burnInProtectionEnabled = context.getResources().getBoolean(
|
||||
boolean burnInProtectionEnabled = mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_enableBurnInProtection);
|
||||
// Allow a system property to override this. Used by developer settings.
|
||||
boolean burnInProtectionDevMode =
|
||||
@@ -1922,7 +1955,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
maxVertical = -4;
|
||||
maxRadius = (isRoundWindow()) ? 6 : -1;
|
||||
} else {
|
||||
Resources resources = context.getResources();
|
||||
Resources resources = mContext.getResources();
|
||||
minHorizontal = resources.getInteger(
|
||||
com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset);
|
||||
maxHorizontal = resources.getInteger(
|
||||
@@ -1935,21 +1968,21 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
com.android.internal.R.integer.config_burnInProtectionMaxRadius);
|
||||
}
|
||||
mBurnInProtectionHelper = new BurnInProtectionHelper(
|
||||
context, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
|
||||
mContext, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
|
||||
}
|
||||
|
||||
mHandler = new PolicyHandler();
|
||||
mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
|
||||
mSettingsObserver = new SettingsObserver(mHandler);
|
||||
mSettingsObserver.observe();
|
||||
mModifierShortcutManager = new ModifierShortcutManager(context);
|
||||
mUiMode = context.getResources().getInteger(
|
||||
mModifierShortcutManager = new ModifierShortcutManager(mContext);
|
||||
mUiMode = mContext.getResources().getInteger(
|
||||
com.android.internal.R.integer.config_defaultUiModeType);
|
||||
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
|
||||
mHomeIntent.addCategory(Intent.CATEGORY_HOME);
|
||||
mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
||||
mEnableCarDockHomeCapture = context.getResources().getBoolean(
|
||||
mEnableCarDockHomeCapture = mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_enableCarDockHomeLaunch);
|
||||
mCarDockIntent = new Intent(Intent.ACTION_MAIN, null);
|
||||
mCarDockIntent.addCategory(Intent.CATEGORY_CAR_DOCK);
|
||||
@@ -1964,7 +1997,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
mVrHeadsetHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
||||
|
||||
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
|
||||
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
|
||||
mBroadcastWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||
"PhoneWindowManager.mBroadcastWakeLock");
|
||||
mPowerKeyWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||
@@ -2036,9 +2069,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
|
||||
readConfigurationDependentBehaviors();
|
||||
|
||||
mDisplayFoldController = DisplayFoldController.create(context, DEFAULT_DISPLAY);
|
||||
mDisplayFoldController = DisplayFoldController.create(mContext, DEFAULT_DISPLAY);
|
||||
|
||||
mAccessibilityManager = (AccessibilityManager) context.getSystemService(
|
||||
mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(
|
||||
Context.ACCESSIBILITY_SERVICE);
|
||||
|
||||
// register for dock events
|
||||
@@ -2048,7 +2081,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
filter.addAction(UiModeManager.ACTION_ENTER_DESK_MODE);
|
||||
filter.addAction(UiModeManager.ACTION_EXIT_DESK_MODE);
|
||||
filter.addAction(Intent.ACTION_DOCK_EVENT);
|
||||
Intent intent = context.registerReceiver(mDockReceiver, filter);
|
||||
Intent intent = mContext.registerReceiver(mDockReceiver, filter);
|
||||
if (intent != null) {
|
||||
// Retrieve current sticky dock event broadcast.
|
||||
mDefaultDisplayPolicy.setDockMode(intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
|
||||
@@ -2059,13 +2092,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_DREAMING_STARTED);
|
||||
filter.addAction(Intent.ACTION_DREAMING_STOPPED);
|
||||
context.registerReceiver(mDreamReceiver, filter);
|
||||
mContext.registerReceiver(mDreamReceiver, filter);
|
||||
|
||||
// register for multiuser-relevant broadcasts
|
||||
filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
|
||||
context.registerReceiver(mMultiuserReceiver, filter);
|
||||
mContext.registerReceiver(mMultiuserReceiver, filter);
|
||||
|
||||
mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
|
||||
com.android.internal.R.array.config_safeModeEnabledVibePattern);
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.server.policy;
|
||||
|
||||
import static android.view.KeyEvent.KEYCODE_POWER;
|
||||
import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN;
|
||||
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
|
||||
|
||||
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS;
|
||||
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
|
||||
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.test.filters.MediumTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Test class for combination key shortcuts.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest WmTests:CombinationKeyTests
|
||||
*/
|
||||
@MediumTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CombinationKeyTests extends ShortcutKeyTestBase {
|
||||
private static final long A11Y_KEY_HOLD_MILLIS = 3500;
|
||||
|
||||
/**
|
||||
* Power-VolDown to take screenshot.
|
||||
*/
|
||||
@Test
|
||||
public void testPowerVolumeDown() {
|
||||
sendKeyCombination(new int[]{KEYCODE_POWER, KEYCODE_VOLUME_DOWN},
|
||||
ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout());
|
||||
mPhoneWindowManager.assertTakeScreenshotCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Power-VolUp to show global actions or mute audio. (Phone default behavior)
|
||||
*/
|
||||
@Test
|
||||
public void testPowerVolumeUp() {
|
||||
// Show global actions.
|
||||
mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS);
|
||||
sendKeyCombination(new int[]{KEYCODE_POWER, KEYCODE_VOLUME_UP}, 0);
|
||||
mPhoneWindowManager.assertShowGlobalActionsCalled();
|
||||
|
||||
// Mute audio (hold over 100ms).
|
||||
mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE);
|
||||
sendKeyCombination(new int[]{KEYCODE_POWER, KEYCODE_VOLUME_UP}, 100);
|
||||
mPhoneWindowManager.assertVolumeMute();
|
||||
}
|
||||
|
||||
/**
|
||||
* VolDown-VolUp and hold 3 secs to enable accessibility service.
|
||||
*/
|
||||
@Test
|
||||
public void testVolumeDownVolumeUp() {
|
||||
sendKeyCombination(new int[]{KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP}, A11Y_KEY_HOLD_MILLIS);
|
||||
mPhoneWindowManager.assertAccessibilityKeychordCalled();
|
||||
}
|
||||
}
|
||||
@@ -43,11 +43,11 @@ import java.util.concurrent.TimeUnit;
|
||||
* Test class for {@link KeyCombinationManager}.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest KeyCombinationTests
|
||||
* atest KeyCombinationManagerTests
|
||||
*/
|
||||
|
||||
@SmallTest
|
||||
public class KeyCombinationTests {
|
||||
public class KeyCombinationManagerTests {
|
||||
private KeyCombinationManager mKeyCombinationManager;
|
||||
|
||||
private final CountDownLatch mAction1Triggered = new CountDownLatch(1);
|
||||
@@ -228,4 +228,4 @@ public class KeyCombinationTests {
|
||||
mKeyCombinationManager.interceptKey(firstKeyDown, true);
|
||||
assertTrue(mKeyCombinationManager.getKeyInterceptTimeout(KEYCODE_VOLUME_UP) > eventTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.server.policy;
|
||||
|
||||
import static android.view.KeyEvent.KEYCODE_POWER;
|
||||
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
|
||||
|
||||
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
|
||||
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
|
||||
|
||||
import android.view.Display;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test class for power key gesture.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest WmTests:PowerKeyGestureTests
|
||||
*/
|
||||
public class PowerKeyGestureTests extends ShortcutKeyTestBase {
|
||||
/**
|
||||
* Power single press to turn screen on/off.
|
||||
*/
|
||||
@Test
|
||||
public void testPowerSinglePress() {
|
||||
sendKey(KEYCODE_POWER);
|
||||
mPhoneWindowManager.assertPowerSleep();
|
||||
|
||||
// turn screen on when begin from non-interactive.
|
||||
mPhoneWindowManager.overrideDisplayState(Display.STATE_OFF);
|
||||
sendKey(KEYCODE_POWER);
|
||||
mPhoneWindowManager.assertPowerWakeUp();
|
||||
mPhoneWindowManager.assertNoPowerSleep();
|
||||
}
|
||||
|
||||
/**
|
||||
* Power double press to trigger camera.
|
||||
*/
|
||||
@Test
|
||||
public void testPowerDoublePress() {
|
||||
sendKey(KEYCODE_POWER);
|
||||
sendKey(KEYCODE_POWER);
|
||||
mPhoneWindowManager.assertCameraLaunch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Power long press to show assistant or global actions.
|
||||
*/
|
||||
@Test
|
||||
public void testPowerLongPress() {
|
||||
// Show assistant.
|
||||
mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_ASSISTANT);
|
||||
sendKey(KEYCODE_POWER, true);
|
||||
mPhoneWindowManager.assertAssistLaunch();
|
||||
|
||||
// Show global actions.
|
||||
mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_GLOBAL_ACTIONS);
|
||||
sendKey(KEYCODE_POWER, true);
|
||||
mPhoneWindowManager.assertShowGlobalActionsCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore power press if combination key already triggered.
|
||||
*/
|
||||
@Test
|
||||
public void testIgnoreSinglePressWhenCombinationKeyTriggered() {
|
||||
sendKeyCombination(new int[]{KEYCODE_POWER, KEYCODE_VOLUME_UP}, 0);
|
||||
mPhoneWindowManager.assertNoPowerSleep();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.server.policy;
|
||||
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.KeyEvent.KEYCODE_ALT_LEFT;
|
||||
import static android.view.KeyEvent.KEYCODE_ALT_RIGHT;
|
||||
import static android.view.KeyEvent.KEYCODE_CTRL_LEFT;
|
||||
import static android.view.KeyEvent.KEYCODE_CTRL_RIGHT;
|
||||
import static android.view.KeyEvent.KEYCODE_META_LEFT;
|
||||
import static android.view.KeyEvent.KEYCODE_META_RIGHT;
|
||||
import static android.view.KeyEvent.KEYCODE_SHIFT_LEFT;
|
||||
import static android.view.KeyEvent.KEYCODE_SHIFT_RIGHT;
|
||||
import static android.view.KeyEvent.META_ALT_LEFT_ON;
|
||||
import static android.view.KeyEvent.META_ALT_ON;
|
||||
import static android.view.KeyEvent.META_ALT_RIGHT_ON;
|
||||
import static android.view.KeyEvent.META_CTRL_LEFT_ON;
|
||||
import static android.view.KeyEvent.META_CTRL_ON;
|
||||
import static android.view.KeyEvent.META_CTRL_RIGHT_ON;
|
||||
import static android.view.KeyEvent.META_META_LEFT_ON;
|
||||
import static android.view.KeyEvent.META_META_ON;
|
||||
import static android.view.KeyEvent.META_META_RIGHT_ON;
|
||||
import static android.view.KeyEvent.META_SHIFT_LEFT_ON;
|
||||
import static android.view.KeyEvent.META_SHIFT_ON;
|
||||
import static android.view.KeyEvent.META_SHIFT_RIGHT_ON;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
|
||||
|
||||
import static java.util.Collections.unmodifiableMap;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class ShortcutKeyTestBase {
|
||||
TestPhoneWindowManager mPhoneWindowManager;
|
||||
final Context mContext = getInstrumentation().getTargetContext();
|
||||
|
||||
/** Modifier key to meta state */
|
||||
private static final Map<Integer, Integer> MODIFIER;
|
||||
static {
|
||||
final Map<Integer, Integer> map = new ArrayMap<>();
|
||||
map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON);
|
||||
map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON);
|
||||
map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON);
|
||||
map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON);
|
||||
map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON);
|
||||
map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON);
|
||||
map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON);
|
||||
map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON);
|
||||
|
||||
MODIFIER = unmodifiableMap(map);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
if (Looper.myLooper() == null) {
|
||||
Looper.prepare();
|
||||
}
|
||||
|
||||
mPhoneWindowManager = new TestPhoneWindowManager(mContext);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mPhoneWindowManager.tearDown();
|
||||
}
|
||||
|
||||
void sendKeyCombination(int[] keyCodes, long duration) {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final int count = keyCodes.length;
|
||||
final KeyEvent[] events = new KeyEvent[count];
|
||||
int metaState = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int keyCode = keyCodes[i];
|
||||
final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
|
||||
0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
|
||||
0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
|
||||
event.setDisplayId(DEFAULT_DISPLAY);
|
||||
events[i] = event;
|
||||
// The order is important here, metaState could be updated and applied to the next key.
|
||||
metaState |= MODIFIER.getOrDefault(keyCode, 0);
|
||||
}
|
||||
|
||||
for (KeyEvent event: events) {
|
||||
interceptKey(event);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(duration);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
for (KeyEvent event: events) {
|
||||
final long eventTime = SystemClock.uptimeMillis();
|
||||
final int keyCode = event.getKeyCode();
|
||||
final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
|
||||
0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/,
|
||||
InputDevice.SOURCE_KEYBOARD);
|
||||
interceptKey(upEvent);
|
||||
metaState &= ~MODIFIER.getOrDefault(keyCode, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void sendKey(int keyCode) {
|
||||
sendKey(keyCode, false);
|
||||
}
|
||||
|
||||
void sendKey(int keyCode, boolean longPress) {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
|
||||
0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
|
||||
0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
|
||||
event.setDisplayId(DEFAULT_DISPLAY);
|
||||
interceptKey(event);
|
||||
|
||||
if (longPress) {
|
||||
final long nextDownTime = downTime + ViewConfiguration.getLongPressTimeout();
|
||||
final KeyEvent nextDownevent = new KeyEvent(downTime, nextDownTime,
|
||||
KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, 0 /*metaState*/,
|
||||
KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
|
||||
KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD);
|
||||
interceptKey(nextDownevent);
|
||||
}
|
||||
|
||||
final long eventTime = longPress
|
||||
? SystemClock.uptimeMillis() + ViewConfiguration.getLongPressTimeout()
|
||||
: SystemClock.uptimeMillis();
|
||||
final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
|
||||
0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
|
||||
0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
|
||||
interceptKey(upEvent);
|
||||
}
|
||||
|
||||
private void interceptKey(KeyEvent keyEvent) {
|
||||
int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent);
|
||||
if ((actions & ACTION_PASS_TO_USER) != 0) {
|
||||
if (0 == mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent)) {
|
||||
mPhoneWindowManager.dispatchUnhandledKey(keyEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.policy;
|
||||
|
||||
import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
|
||||
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.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;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
|
||||
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.LONG_PRESS_POWER_GO_TO_VOICE_ASSIST;
|
||||
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_NOTHING;
|
||||
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_SHUT_OFF;
|
||||
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.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.hardware.display.DisplayManagerInternal;
|
||||
import android.hardware.input.InputManagerInternal;
|
||||
import android.media.AudioManagerInternal;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManagerInternal;
|
||||
import android.os.Vibrator;
|
||||
import android.service.dreams.DreamManagerInternal;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.view.Display;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.autofill.AutofillManagerInternal;
|
||||
|
||||
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
|
||||
import com.android.internal.accessibility.AccessibilityShortcutController;
|
||||
import com.android.server.GestureLauncherService;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.vr.VrManagerInternal;
|
||||
import com.android.server.wm.ActivityTaskManagerInternal;
|
||||
import com.android.server.wm.DisplayPolicy;
|
||||
import com.android.server.wm.DisplayRotation;
|
||||
import com.android.server.wm.WindowManagerInternal;
|
||||
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockSettings;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
class TestPhoneWindowManager {
|
||||
private static final long SHORTCUT_KEY_DELAY_MILLIS = 150;
|
||||
|
||||
private PhoneWindowManager mPhoneWindowManager;
|
||||
private Context mContext;
|
||||
|
||||
@Mock private WindowManagerInternal mWindowManagerInternal;
|
||||
@Mock private ActivityManagerInternal mActivityManagerInternal;
|
||||
@Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
|
||||
@Mock private InputManagerInternal mInputManagerInternal;
|
||||
@Mock private DreamManagerInternal mDreamManagerInternal;
|
||||
@Mock private PowerManagerInternal mPowerManagerInternal;
|
||||
@Mock private DisplayManagerInternal mDisplayManagerInternal;
|
||||
@Mock private AppOpsManager mAppOpsManager;
|
||||
@Mock private DisplayManager mDisplayManager;
|
||||
@Mock private PackageManager mPackageManager;
|
||||
@Mock private TelecomManager mTelecomManager;
|
||||
@Mock private NotificationManager mNotificationManager;
|
||||
@Mock private Vibrator mVibrator;
|
||||
@Mock private PowerManager mPowerManager;
|
||||
@Mock private WindowManagerPolicy.WindowManagerFuncs mWindowManagerFuncsImpl;
|
||||
@Mock private AudioManagerInternal mAudioManagerInternal;
|
||||
@Mock private SearchManager mSearchManager;
|
||||
|
||||
@Mock private Display mDisplay;
|
||||
@Mock private DisplayRotation mDisplayRotation;
|
||||
@Mock private DisplayPolicy mDisplayPolicy;
|
||||
@Mock private WindowManagerPolicy.ScreenOnListener mScreenOnListener;
|
||||
@Mock private GestureLauncherService mGestureLauncherService;
|
||||
@Mock private GlobalActions mGlobalActions;
|
||||
@Mock private AccessibilityShortcutController mAccessibilityShortcutController;
|
||||
|
||||
private StaticMockitoSession mMockitoSession;
|
||||
private HandlerThread mHandlerThread;
|
||||
private Handler mHandler;
|
||||
|
||||
private class TestInjector extends PhoneWindowManager.Injector {
|
||||
TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
|
||||
super(context, funcs);
|
||||
}
|
||||
|
||||
AccessibilityShortcutController getAccessibilityShortcutController(
|
||||
Context context, Handler handler, int initialUserId) {
|
||||
return mAccessibilityShortcutController;
|
||||
}
|
||||
|
||||
GlobalActions getGlobalActions() {
|
||||
return mGlobalActions;
|
||||
}
|
||||
}
|
||||
|
||||
TestPhoneWindowManager(Context context) {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mHandlerThread = new HandlerThread("fake window manager");
|
||||
mHandlerThread.start();
|
||||
mHandler = new Handler(mHandlerThread.getLooper());
|
||||
mHandler.runWithScissors(()-> setUp(context), 0 /* timeout */);
|
||||
}
|
||||
|
||||
private void setUp(Context context) {
|
||||
mPhoneWindowManager = spy(new PhoneWindowManager());
|
||||
mContext = spy(context);
|
||||
|
||||
// Use stubOnly() to reduce memory usage if it doesn't need verification.
|
||||
final MockSettings spyStubOnly = withSettings().stubOnly()
|
||||
.defaultAnswer(CALLS_REAL_METHODS);
|
||||
// Return mocked services: LocalServices.getService
|
||||
mMockitoSession = mockitoSession()
|
||||
.mockStatic(LocalServices.class, spyStubOnly)
|
||||
.startMocking();
|
||||
|
||||
doReturn(mWindowManagerInternal).when(
|
||||
() -> LocalServices.getService(eq(WindowManagerInternal.class)));
|
||||
doReturn(mActivityManagerInternal).when(
|
||||
() -> LocalServices.getService(eq(ActivityManagerInternal.class)));
|
||||
doReturn(mActivityTaskManagerInternal).when(
|
||||
() -> LocalServices.getService(eq(ActivityTaskManagerInternal.class)));
|
||||
doReturn(mInputManagerInternal).when(
|
||||
() -> LocalServices.getService(eq(InputManagerInternal.class)));
|
||||
doReturn(mDreamManagerInternal).when(
|
||||
() -> LocalServices.getService(eq(DreamManagerInternal.class)));
|
||||
doReturn(mPowerManagerInternal).when(
|
||||
() -> LocalServices.getService(eq(PowerManagerInternal.class)));
|
||||
doReturn(mDisplayManagerInternal).when(
|
||||
() -> LocalServices.getService(eq(DisplayManagerInternal.class)));
|
||||
doReturn(mGestureLauncherService).when(
|
||||
() -> LocalServices.getService(eq(GestureLauncherService.class)));
|
||||
doReturn(null).when(() -> LocalServices.getService(eq(VrManagerInternal.class)));
|
||||
doReturn(null).when(() -> LocalServices.getService(eq(AutofillManagerInternal.class)));
|
||||
|
||||
doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class));
|
||||
doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
|
||||
doReturn(mPackageManager).when(mContext).getPackageManager();
|
||||
doReturn(false).when(mPackageManager).hasSystemFeature(any());
|
||||
try {
|
||||
doThrow(new PackageManager.NameNotFoundException("test")).when(mPackageManager)
|
||||
.getActivityInfo(any(), anyInt());
|
||||
doReturn(new String[] { "testPackage" }).when(mPackageManager)
|
||||
.canonicalToCurrentPackageNames(any());
|
||||
} catch (PackageManager.NameNotFoundException ignored) { }
|
||||
|
||||
doReturn(mTelecomManager).when(mContext).getSystemService(eq(Context.TELECOM_SERVICE));
|
||||
doReturn(mNotificationManager).when(mContext)
|
||||
.getSystemService(eq(NotificationManager.class));
|
||||
doReturn(mVibrator).when(mContext).getSystemService(eq(Context.VIBRATOR_SERVICE));
|
||||
|
||||
final PowerManager.WakeLock wakeLock = mock(PowerManager.WakeLock.class);
|
||||
doReturn(wakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
|
||||
doReturn(mPowerManager).when(mContext).getSystemService(eq(Context.POWER_SERVICE));
|
||||
doReturn(true).when(mPowerManager).isInteractive();
|
||||
|
||||
doReturn(mDisplay).when(mDisplayManager).getDisplay(eq(DEFAULT_DISPLAY));
|
||||
doReturn(STATE_ON).when(mDisplay).getState();
|
||||
doReturn(true).when(mDisplayPolicy).isAwake();
|
||||
doNothing().when(mDisplayPolicy).takeScreenshot(anyInt(), anyInt());
|
||||
doReturn(mDisplayPolicy).when(mDisplayRotation).getDisplayPolicy();
|
||||
doReturn(mScreenOnListener).when(mDisplayPolicy).getScreenOnListener();
|
||||
mPhoneWindowManager.setDefaultDisplay(new WindowManagerPolicy.DisplayContentInfo() {
|
||||
@Override
|
||||
public DisplayRotation getDisplayRotation() {
|
||||
return mDisplayRotation;
|
||||
}
|
||||
@Override
|
||||
public Display getDisplay() {
|
||||
return mDisplay;
|
||||
}
|
||||
});
|
||||
|
||||
doNothing().when(mPhoneWindowManager).initializeHdmiState();
|
||||
doNothing().when(mPhoneWindowManager).updateSettings();
|
||||
doNothing().when(mPhoneWindowManager).screenTurningOn(anyInt(), any());
|
||||
doNothing().when(mPhoneWindowManager).screenTurnedOn(anyInt());
|
||||
doNothing().when(mPhoneWindowManager).startedWakingUp(anyInt());
|
||||
doNothing().when(mPhoneWindowManager).finishedWakingUp(anyInt());
|
||||
|
||||
mPhoneWindowManager.init(new TestInjector(mContext, mWindowManagerFuncsImpl));
|
||||
mPhoneWindowManager.systemReady();
|
||||
mPhoneWindowManager.systemBooted();
|
||||
|
||||
overrideLaunchAccessibility();
|
||||
}
|
||||
|
||||
void tearDown() {
|
||||
mHandlerThread.quitSafely();
|
||||
mMockitoSession.finishMocking();
|
||||
}
|
||||
|
||||
// Override accessibility setting and perform function.
|
||||
private void overrideLaunchAccessibility() {
|
||||
doReturn(true).when(mAccessibilityShortcutController)
|
||||
.isAccessibilityShortcutAvailable(anyBoolean());
|
||||
doNothing().when(mAccessibilityShortcutController).performAccessibilityShortcut();
|
||||
}
|
||||
|
||||
int interceptKeyBeforeQueueing(KeyEvent event) {
|
||||
return mPhoneWindowManager.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE);
|
||||
}
|
||||
|
||||
long interceptKeyBeforeDispatching(KeyEvent event) {
|
||||
return mPhoneWindowManager.interceptKeyBeforeDispatching(null /*focusedToken*/,
|
||||
event, FLAG_INTERACTIVE);
|
||||
}
|
||||
|
||||
void dispatchUnhandledKey(KeyEvent event) {
|
||||
mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
|
||||
}
|
||||
|
||||
void waitForIdle() {
|
||||
mHandler.runWithScissors(() -> { }, 0 /* timeout */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Below functions will override the setting or the policy behavior.
|
||||
*/
|
||||
void overridePowerVolumeUp(int behavior) {
|
||||
mPhoneWindowManager.mPowerVolUpBehavior = behavior;
|
||||
|
||||
// override mRingerToggleChord as mute so we could trigger the behavior.
|
||||
if (behavior == POWER_VOLUME_UP_BEHAVIOR_MUTE) {
|
||||
mPhoneWindowManager.mRingerToggleChord = VOLUME_HUSH_MUTE;
|
||||
doReturn(mAudioManagerInternal).when(
|
||||
() -> LocalServices.getService(eq(AudioManagerInternal.class)));
|
||||
}
|
||||
}
|
||||
|
||||
// Override assist perform function.
|
||||
void overrideLongPressOnPower(int behavior) {
|
||||
mPhoneWindowManager.mLongPressOnPowerBehavior = behavior;
|
||||
|
||||
switch (behavior) {
|
||||
case LONG_PRESS_POWER_NOTHING:
|
||||
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
|
||||
case LONG_PRESS_POWER_SHUT_OFF:
|
||||
case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
|
||||
case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
|
||||
break;
|
||||
case LONG_PRESS_POWER_ASSISTANT:
|
||||
doNothing().when(mPhoneWindowManager).sendCloseSystemWindows();
|
||||
doReturn(true).when(mPhoneWindowManager).isUserSetupComplete();
|
||||
doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
|
||||
doReturn(mSearchManager).when(mContext)
|
||||
.getSystemService(eq(Context.SEARCH_SERVICE));
|
||||
mPhoneWindowManager.mLongPressOnPowerAssistantTimeoutMs = 500;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void overrideDisplayState(int state) {
|
||||
doReturn(state).when(mDisplay).getState();
|
||||
Mockito.reset(mPowerManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Below functions will check the policy behavior could be invoked.
|
||||
*/
|
||||
void assertTakeScreenshotCalled() {
|
||||
waitForIdle();
|
||||
verify(mDisplayPolicy, timeout(SHORTCUT_KEY_DELAY_MILLIS))
|
||||
.takeScreenshot(anyInt(), anyInt());
|
||||
}
|
||||
|
||||
void assertShowGlobalActionsCalled() {
|
||||
waitForIdle();
|
||||
verify(mPhoneWindowManager).showGlobalActions();
|
||||
verify(mGlobalActions, timeout(SHORTCUT_KEY_DELAY_MILLIS))
|
||||
.showDialog(anyBoolean(), anyBoolean());
|
||||
verify(mPowerManager, timeout(SHORTCUT_KEY_DELAY_MILLIS))
|
||||
.userActivity(anyLong(), anyBoolean());
|
||||
}
|
||||
|
||||
void assertVolumeMute() {
|
||||
waitForIdle();
|
||||
verify(mAudioManagerInternal, timeout(SHORTCUT_KEY_DELAY_MILLIS))
|
||||
.silenceRingerModeInternal(eq("volume_hush"));
|
||||
}
|
||||
|
||||
void assertAccessibilityKeychordCalled() {
|
||||
waitForIdle();
|
||||
verify(mAccessibilityShortcutController,
|
||||
timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut();
|
||||
}
|
||||
|
||||
void assertPowerSleep() {
|
||||
waitForIdle();
|
||||
verify(mPowerManager,
|
||||
timeout(SHORTCUT_KEY_DELAY_MILLIS)).goToSleep(anyLong(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
void assertPowerWakeUp() {
|
||||
waitForIdle();
|
||||
verify(mPowerManager,
|
||||
timeout(SHORTCUT_KEY_DELAY_MILLIS)).wakeUp(anyLong(), anyInt(), anyString());
|
||||
}
|
||||
|
||||
void assertNoPowerSleep() {
|
||||
waitForIdle();
|
||||
verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
void assertCameraLaunch() {
|
||||
waitForIdle();
|
||||
// GestureLauncherService should receive interceptPowerKeyDown twice.
|
||||
verify(mGestureLauncherService, times(2))
|
||||
.interceptPowerKeyDown(any(), anyBoolean(), any());
|
||||
}
|
||||
|
||||
void assertAssistLaunch() {
|
||||
waitForIdle();
|
||||
verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user