1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.policy; 17 18 import static android.view.Display.DEFAULT_DISPLAY; 19 import static android.view.KeyEvent.KEYCODE_ALT_LEFT; 20 import static android.view.KeyEvent.KEYCODE_ALT_RIGHT; 21 import static android.view.KeyEvent.KEYCODE_CTRL_LEFT; 22 import static android.view.KeyEvent.KEYCODE_CTRL_RIGHT; 23 import static android.view.KeyEvent.KEYCODE_META_LEFT; 24 import static android.view.KeyEvent.KEYCODE_META_RIGHT; 25 import static android.view.KeyEvent.KEYCODE_SHIFT_LEFT; 26 import static android.view.KeyEvent.KEYCODE_SHIFT_RIGHT; 27 import static android.view.KeyEvent.META_ALT_LEFT_ON; 28 import static android.view.KeyEvent.META_ALT_ON; 29 import static android.view.KeyEvent.META_ALT_RIGHT_ON; 30 import static android.view.KeyEvent.META_CTRL_LEFT_ON; 31 import static android.view.KeyEvent.META_CTRL_ON; 32 import static android.view.KeyEvent.META_CTRL_RIGHT_ON; 33 import static android.view.KeyEvent.META_META_LEFT_ON; 34 import static android.view.KeyEvent.META_META_ON; 35 import static android.view.KeyEvent.META_META_RIGHT_ON; 36 import static android.view.KeyEvent.META_SHIFT_LEFT_ON; 37 import static android.view.KeyEvent.META_SHIFT_ON; 38 import static android.view.KeyEvent.META_SHIFT_RIGHT_ON; 39 40 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 41 42 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 43 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; 44 import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER; 45 46 import static org.mockito.ArgumentMatchers.eq; 47 48 import static java.util.Collections.unmodifiableMap; 49 50 import android.content.Context; 51 import android.content.res.Resources; 52 import android.platform.test.flag.junit.SetFlagsRule; 53 import android.util.ArrayMap; 54 import android.view.InputDevice; 55 import android.view.KeyCharacterMap; 56 import android.view.KeyEvent; 57 import android.view.ViewConfiguration; 58 59 import com.android.internal.util.test.FakeSettingsProvider; 60 import com.android.internal.util.test.FakeSettingsProviderRule; 61 62 import org.junit.After; 63 import org.junit.Before; 64 import org.junit.Rule; 65 import org.junit.rules.RuleChain; 66 67 import java.util.Map; 68 69 class ShortcutKeyTestBase { 70 71 public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 72 public final FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); 73 74 @Rule 75 public RuleChain rules = RuleChain.outerRule(mSettingsProviderRule).around(mSetFlagsRule); 76 77 private Resources mResources; 78 TestPhoneWindowManager mPhoneWindowManager; 79 DispatchedKeyHandler mDispatchedKeyHandler = event -> false; 80 Context mContext; 81 82 /** Modifier key to meta state */ 83 protected static final Map<Integer, Integer> MODIFIER; 84 static { 85 final Map<Integer, Integer> map = new ArrayMap<>(); map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON)86 map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON); map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON)87 map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON); map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON)88 map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON); map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON)89 map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON); map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON)90 map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON); map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON)91 map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON); map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON)92 map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON); map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON)93 map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON); 94 95 MODIFIER = unmodifiableMap(map); 96 } 97 98 @Before setup()99 public void setup() { 100 mContext = spy(getInstrumentation().getTargetContext()); 101 mResources = spy(mContext.getResources()); 102 doReturn(mResources).when(mContext).getResources(); 103 doReturn(mSettingsProviderRule.mockContentResolver(mContext)) 104 .when(mContext).getContentResolver(); 105 } 106 107 108 /** Same as {@link setUpPhoneWindowManager(boolean)}, without supporting settings update. */ setUpPhoneWindowManager()109 protected final void setUpPhoneWindowManager() { 110 setUpPhoneWindowManager(/* supportSettingsUpdate= */ false); 111 } 112 113 /** 114 * Creates and sets up a {@link TestPhoneWindowManager} instance. 115 * 116 * <p>Subclasses must call this at the start of the test if they intend to interact with phone 117 * window manager. 118 * 119 * @param supportSettingsUpdate {@code true} to have PWM respond to any Settings changes upon 120 * instantiation. Although this is supposed to also allow a test to listen to any Settings 121 * changes after instantiation, MockContentResolver in this class's setup stubs out 122 * notifyChange(), which prevents SettingsObserver from getting notified of events. So 123 * we're effectively always instantiating TestPhoneWindowManager with 124 * supportSettingsUpdate=false. 125 */ setUpPhoneWindowManager(boolean supportSettingsUpdate)126 protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) { 127 mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate); 128 } 129 setDispatchedKeyHandler(DispatchedKeyHandler keyHandler)130 protected final void setDispatchedKeyHandler(DispatchedKeyHandler keyHandler) { 131 mDispatchedKeyHandler = keyHandler; 132 } 133 134 @After tearDown()135 public void tearDown() { 136 if (mPhoneWindowManager != null) { 137 mPhoneWindowManager.tearDown(); 138 } 139 } 140 sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress, int displayId)141 void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress, int displayId) { 142 final long downTime = mPhoneWindowManager.getCurrentTime(); 143 final int count = keyCodes.length; 144 int metaState = 0; 145 146 for (int i = 0; i < count; i++) { 147 final int keyCode = keyCodes[i]; 148 final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 149 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 150 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); 151 event.setDisplayId(displayId); 152 interceptKey(event); 153 // The order is important here, metaState could be updated and applied to the next key. 154 metaState |= MODIFIER.getOrDefault(keyCode, 0); 155 } 156 157 if (durationMillis > 0) { 158 mPhoneWindowManager.moveTimeForward(durationMillis); 159 } 160 161 if (longPress) { 162 final long nextDownTime = mPhoneWindowManager.getCurrentTime(); 163 for (int i = 0; i < count; i++) { 164 final int keyCode = keyCodes[i]; 165 final KeyEvent nextDownEvent = new KeyEvent(downTime, nextDownTime, 166 KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, metaState, 167 KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 168 KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD); 169 nextDownEvent.setDisplayId(displayId); 170 interceptKey(nextDownEvent); 171 } 172 } 173 174 final long eventTime = mPhoneWindowManager.getCurrentTime(); 175 for (int i = count - 1; i >= 0; i--) { 176 final int keyCode = keyCodes[i]; 177 final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode, 178 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, 179 InputDevice.SOURCE_KEYBOARD); 180 upEvent.setDisplayId(displayId); 181 interceptKey(upEvent); 182 metaState &= ~MODIFIER.getOrDefault(keyCode, 0); 183 } 184 } 185 sendKeyCombination(int[] keyCodes, long durationMillis)186 void sendKeyCombination(int[] keyCodes, long durationMillis) { 187 sendKeyCombination(keyCodes, durationMillis, false /* longPress */, DEFAULT_DISPLAY); 188 } 189 sendKeyCombination(int[] keyCodes, long durationMillis, int displayId)190 void sendKeyCombination(int[] keyCodes, long durationMillis, int displayId) { 191 sendKeyCombination(keyCodes, durationMillis, false /* longPress */, displayId); 192 } 193 sendLongPressKeyCombination(int[] keyCodes)194 void sendLongPressKeyCombination(int[] keyCodes) { 195 sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */, 196 DEFAULT_DISPLAY); 197 } 198 sendKey(int keyCode)199 void sendKey(int keyCode) { 200 sendKey(keyCode, false); 201 } 202 sendKey(int keyCode, boolean longPress)203 void sendKey(int keyCode, boolean longPress) { 204 sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY); 205 } 206 207 /** 208 * Since we use SettingsProviderRule to mock the ContentResolver in these 209 * tests, the settings observer registered by PhoneWindowManager will not 210 * be triggered automatically by the mock. Use this method to force the 211 * settings observer change after modifying any settings. 212 */ triggerSettingsObserverChange()213 void triggerSettingsObserverChange() { 214 mPhoneWindowManager.getSettingsObserver().onChange( 215 // This boolean doesn't matter. This observer does the same thing regardless. 216 /*selfChange=*/true); 217 } 218 219 /** Override a resource's return value. */ overrideResource(int resId, int expectedBehavior)220 void overrideResource(int resId, int expectedBehavior) { 221 doReturn(expectedBehavior).when(mResources).getInteger(eq(resId)); 222 } 223 interceptKey(KeyEvent keyEvent)224 private void interceptKey(KeyEvent keyEvent) { 225 int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent); 226 if ((actions & ACTION_PASS_TO_USER) != 0) { 227 if (0 == mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent)) { 228 if (!mDispatchedKeyHandler.onKeyDispatched(keyEvent)) { 229 mPhoneWindowManager.dispatchUnhandledKey(keyEvent); 230 } 231 } 232 } 233 mPhoneWindowManager.dispatchAllPendingEvents(); 234 } 235 236 interface DispatchedKeyHandler { 237 /** 238 * Called when a key event is dispatched to app. 239 * 240 * @return true if the event is consumed by app. 241 */ onKeyDispatched(KeyEvent event)242 boolean onKeyDispatched(KeyEvent event); 243 } 244 } 245