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.anyInt; 47 import static org.mockito.ArgumentMatchers.any; 48 import static org.mockito.ArgumentMatchers.eq; 49 50 import static java.util.Collections.unmodifiableMap; 51 52 import android.content.ComponentName; 53 import android.content.Context; 54 import android.content.pm.ActivityInfo; 55 import android.content.pm.ApplicationInfo; 56 import android.content.pm.PackageManager; 57 import android.content.res.Resources; 58 import android.content.res.XmlResourceParser; 59 import android.hardware.input.KeyGestureEvent; 60 import android.platform.test.flag.junit.SetFlagsRule; 61 import android.util.ArrayMap; 62 import android.view.InputDevice; 63 import android.view.KeyCharacterMap; 64 import android.view.KeyEvent; 65 import android.view.ViewConfiguration; 66 67 import com.android.internal.util.test.FakeSettingsProvider; 68 import com.android.internal.util.test.FakeSettingsProviderRule; 69 70 import org.junit.After; 71 import org.junit.Before; 72 import org.junit.Rule; 73 import org.junit.rules.RuleChain; 74 75 import java.util.Map; 76 77 class ShortcutKeyTestBase { 78 79 public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 80 public final FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); 81 82 @Rule 83 public RuleChain rules = RuleChain.outerRule(mSettingsProviderRule).around(mSetFlagsRule); 84 85 private Resources mResources; 86 private PackageManager mPackageManager; 87 TestPhoneWindowManager mPhoneWindowManager; 88 DispatchedKeyHandler mDispatchedKeyHandler; 89 private int mDownKeysDispatched; 90 private int mUpKeysDispatched; 91 Context mContext; 92 93 /** Modifier key to meta state */ 94 protected static final Map<Integer, Integer> MODIFIER; 95 static { 96 final Map<Integer, Integer> map = new ArrayMap<>(); map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON)97 map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON); map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON)98 map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON); map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON)99 map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON); map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON)100 map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON); map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON)101 map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON); map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON)102 map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON); map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON)103 map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON); map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON)104 map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON); 105 106 MODIFIER = unmodifiableMap(map); 107 } 108 109 @Before setup()110 public void setup() { 111 mContext = spy(getInstrumentation().getTargetContext()); 112 mResources = spy(mContext.getResources()); 113 mPackageManager = spy(mContext.getPackageManager()); 114 doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); 115 doReturn(mResources).when(mContext).getResources(); 116 doReturn(mSettingsProviderRule.mockContentResolver(mContext)) 117 .when(mContext).getContentResolver(); 118 XmlResourceParser testBookmarks = mResources.getXml( 119 com.android.frameworks.wmtests.R.xml.bookmarks); 120 doReturn(testBookmarks).when(mResources).getXml(com.android.internal.R.xml.bookmarks); 121 mDispatchedKeyHandler = event -> false; 122 mDownKeysDispatched = 0; 123 mUpKeysDispatched = 0; 124 125 try { 126 // Keep packageName / className in sync with 127 // services/tests/wmtests/res/xml/bookmarks.xml 128 ActivityInfo testActivityInfo = new ActivityInfo(); 129 testActivityInfo.applicationInfo = new ApplicationInfo(); 130 testActivityInfo.packageName = 131 testActivityInfo.applicationInfo.packageName = "com.test"; 132 doReturn(testActivityInfo).when(mPackageManager).getActivityInfo( 133 eq(new ComponentName("com.test", "com.test.BookmarkTest")), anyInt()); 134 } catch (PackageManager.NameNotFoundException ignored) { } 135 } 136 137 138 /** Same as {@link setUpPhoneWindowManager(boolean)}, without supporting settings update. */ setUpPhoneWindowManager()139 protected final void setUpPhoneWindowManager() { 140 setUpPhoneWindowManager(/* supportSettingsUpdate= */ false); 141 } 142 143 /** 144 * Creates and sets up a {@link TestPhoneWindowManager} instance. 145 * 146 * <p>Subclasses must call this at the start of the test if they intend to interact with phone 147 * window manager. 148 * 149 * @param supportSettingsUpdate {@code true} to have PWM respond to any Settings changes upon 150 * instantiation. Although this is supposed to also allow a test to listen to any Settings 151 * changes after instantiation, MockContentResolver in this class's setup stubs out 152 * notifyChange(), which prevents SettingsObserver from getting notified of events. So 153 * we're effectively always instantiating TestPhoneWindowManager with 154 * supportSettingsUpdate=false. 155 */ setUpPhoneWindowManager(boolean supportSettingsUpdate)156 protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) { 157 mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate); 158 } 159 setDispatchedKeyHandler(DispatchedKeyHandler keyHandler)160 protected final void setDispatchedKeyHandler(DispatchedKeyHandler keyHandler) { 161 mDispatchedKeyHandler = keyHandler; 162 } 163 164 @After tearDown()165 public void tearDown() { 166 if (mPhoneWindowManager != null) { 167 mPhoneWindowManager.tearDown(); 168 } 169 } 170 sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress, int displayId)171 void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress, int displayId) { 172 final long downTime = mPhoneWindowManager.getCurrentTime(); 173 final int count = keyCodes.length; 174 int metaState = 0; 175 176 for (int i = 0; i < count; i++) { 177 final int keyCode = keyCodes[i]; 178 final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 179 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 180 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); 181 event.setDisplayId(displayId); 182 interceptKey(event); 183 // The order is important here, metaState could be updated and applied to the next key. 184 metaState |= MODIFIER.getOrDefault(keyCode, 0); 185 } 186 187 if (durationMillis > 0) { 188 mPhoneWindowManager.moveTimeForward(durationMillis); 189 } 190 191 if (longPress) { 192 final long nextDownTime = mPhoneWindowManager.getCurrentTime(); 193 for (int i = 0; i < count; i++) { 194 final int keyCode = keyCodes[i]; 195 final KeyEvent nextDownEvent = new KeyEvent(downTime, nextDownTime, 196 KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, metaState, 197 KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 198 KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD); 199 nextDownEvent.setDisplayId(displayId); 200 interceptKey(nextDownEvent); 201 } 202 } 203 204 final long eventTime = mPhoneWindowManager.getCurrentTime(); 205 for (int i = count - 1; i >= 0; i--) { 206 final int keyCode = keyCodes[i]; 207 final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode, 208 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, 209 InputDevice.SOURCE_KEYBOARD); 210 upEvent.setDisplayId(displayId); 211 interceptKey(upEvent); 212 metaState &= ~MODIFIER.getOrDefault(keyCode, 0); 213 } 214 } 215 sendKeyCombination(int[] keyCodes, long durationMillis)216 void sendKeyCombination(int[] keyCodes, long durationMillis) { 217 sendKeyCombination(keyCodes, durationMillis, false /* longPress */, DEFAULT_DISPLAY); 218 } 219 sendKeyCombination(int[] keyCodes, long durationMillis, int displayId)220 void sendKeyCombination(int[] keyCodes, long durationMillis, int displayId) { 221 sendKeyCombination(keyCodes, durationMillis, false /* longPress */, displayId); 222 } 223 sendLongPressKeyCombination(int[] keyCodes)224 void sendLongPressKeyCombination(int[] keyCodes) { 225 sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */, 226 DEFAULT_DISPLAY); 227 } 228 sendKey(int keyCode)229 void sendKey(int keyCode) { 230 sendKey(keyCode, false); 231 } 232 sendKey(int keyCode, boolean longPress)233 void sendKey(int keyCode, boolean longPress) { 234 sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY); 235 } 236 sendKey(int keyCode, long durationMillis)237 void sendKey(int keyCode, long durationMillis) { 238 sendKeyCombination(new int[]{keyCode}, durationMillis, false, DEFAULT_DISPLAY); 239 } 240 sendKeyGestureEventStart(int gestureType)241 void sendKeyGestureEventStart(int gestureType) { 242 mPhoneWindowManager.sendKeyGestureEvent( 243 new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction( 244 KeyGestureEvent.ACTION_GESTURE_START).build()); 245 } 246 sendKeyGestureEventComplete(int gestureType)247 void sendKeyGestureEventComplete(int gestureType) { 248 mPhoneWindowManager.sendKeyGestureEvent( 249 new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction( 250 KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); 251 } 252 sendKeyGestureEventCancel(int gestureType)253 void sendKeyGestureEventCancel(int gestureType) { 254 mPhoneWindowManager.sendKeyGestureEvent( 255 new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction( 256 KeyGestureEvent.ACTION_GESTURE_COMPLETE).setFlags( 257 KeyGestureEvent.FLAG_CANCELLED).build()); 258 } 259 sendKeyGestureEventComplete(int gestureType, int modifierState)260 void sendKeyGestureEventComplete(int gestureType, int modifierState) { 261 mPhoneWindowManager.sendKeyGestureEvent( 262 new KeyGestureEvent.Builder().setModifierState(modifierState).setKeyGestureType( 263 gestureType).setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); 264 } 265 sendKeyGestureEventComplete(int keycode, int modifierState, int gestureType)266 void sendKeyGestureEventComplete(int keycode, int modifierState, int gestureType) { 267 mPhoneWindowManager.sendKeyGestureEvent( 268 new KeyGestureEvent.Builder().setKeycodes(new int[]{keycode}).setModifierState( 269 modifierState).setKeyGestureType(gestureType).setAction( 270 KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); 271 } 272 273 /** 274 * Since we use SettingsProviderRule to mock the ContentResolver in these 275 * tests, the settings observer registered by PhoneWindowManager will not 276 * be triggered automatically by the mock. Use this method to force the 277 * settings observer change after modifying any settings. 278 */ triggerSettingsObserverChange()279 void triggerSettingsObserverChange() { 280 mPhoneWindowManager.getSettingsObserver().onChange( 281 // This boolean doesn't matter. This observer does the same thing regardless. 282 /*selfChange=*/true); 283 } 284 285 /** Override a resource's return value. */ overrideResource(int resId, int expectedBehavior)286 void overrideResource(int resId, int expectedBehavior) { 287 doReturn(expectedBehavior).when(mResources).getInteger(eq(resId)); 288 } 289 getDownKeysDispatched()290 int getDownKeysDispatched() { 291 return mDownKeysDispatched; 292 } 293 getUpKeysDispatched()294 int getUpKeysDispatched() { 295 return mUpKeysDispatched; 296 } 297 interceptKey(KeyEvent keyEvent)298 private void interceptKey(KeyEvent keyEvent) { 299 int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent); 300 if ((actions & ACTION_PASS_TO_USER) != 0) { 301 if (0 == mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent)) { 302 if (!mDispatchedKeyHandler.onKeyDispatched(keyEvent)) { 303 mPhoneWindowManager.interceptUnhandledKey(keyEvent); 304 } 305 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { 306 ++mDownKeysDispatched; 307 } else { 308 ++mUpKeysDispatched; 309 } 310 } 311 } 312 mPhoneWindowManager.dispatchAllPendingEvents(); 313 } 314 315 interface DispatchedKeyHandler { 316 /** 317 * Called when a key event is dispatched to app. 318 * 319 * @return true if the event is consumed by app. 320 */ onKeyDispatched(KeyEvent event)321 boolean onKeyDispatched(KeyEvent event); 322 } 323 } 324