Merge "Introduce system shortcut key unit tests"

This commit is contained in:
Arthur Hung
2022-08-10 05:34:22 +00:00
committed by Android (Google) Code Review
6 changed files with 745 additions and 24 deletions

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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());
}
}