Merge "Introduce system shortcut key unit tests"
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5816e98..f843af8 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -185,6 +185,7 @@
 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 @@
     }
 
     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 @@
         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 @@
         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 @@
                 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 @@
                 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 @@
                         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 @@
         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 @@
 
         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 @@
         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 @@
         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);
 
diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
new file mode 100644
index 0000000..62875e5
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
@@ -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();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
similarity index 98%
rename from services/tests/wmtests/src/com/android/server/policy/KeyCombinationTests.java
rename to services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
index cf57181..4c69857 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
@@ -43,11 +43,11 @@
  * 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 @@
         mKeyCombinationManager.interceptKey(firstKeyDown, true);
         assertTrue(mKeyCombinationManager.getKeyInterceptTimeout(KEYCODE_VOLUME_UP) > eventTime);
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
new file mode 100644
index 0000000..7c6fd05
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -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();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
new file mode 100644
index 0000000..6368f47
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -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);
+            }
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
new file mode 100644
index 0000000..0320efe
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -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());
+    }
+}