• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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