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