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.spy;
43 import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
44 
45 import static java.util.Collections.unmodifiableMap;
46 
47 import android.content.Context;
48 import android.os.Looper;
49 import android.os.SystemClock;
50 import android.util.ArrayMap;
51 import android.view.InputDevice;
52 import android.view.KeyCharacterMap;
53 import android.view.KeyEvent;
54 import android.view.ViewConfiguration;
55 
56 import org.junit.After;
57 import org.junit.Before;
58 
59 import java.util.Map;
60 
61 class ShortcutKeyTestBase {
62     TestPhoneWindowManager mPhoneWindowManager;
63     final Context mContext = spy(getInstrumentation().getTargetContext());
64 
65     /** Modifier key to meta state */
66     protected static final Map<Integer, Integer> MODIFIER;
67     static {
68         final Map<Integer, Integer> map = new ArrayMap<>();
map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON)69         map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON);
map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON)70         map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON);
map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON)71         map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON);
map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON)72         map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON);
map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON)73         map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON);
map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON)74         map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON);
map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON)75         map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON);
map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON)76         map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON);
77 
78         MODIFIER = unmodifiableMap(map);
79     }
80 
81     @Before
setUp()82     public void setUp() {
83         if (Looper.myLooper() == null) {
84             Looper.prepare();
85         }
86 
87         mPhoneWindowManager = new TestPhoneWindowManager(mContext);
88     }
89 
90     @After
tearDown()91     public void tearDown() {
92         mPhoneWindowManager.tearDown();
93     }
94 
sendKeyCombination(int[] keyCodes, long duration)95     void sendKeyCombination(int[] keyCodes, long duration) {
96         final long downTime = SystemClock.uptimeMillis();
97         final int count = keyCodes.length;
98         final KeyEvent[] events = new KeyEvent[count];
99         int metaState = 0;
100         for (int i = 0; i < count; i++) {
101             final int keyCode = keyCodes[i];
102             final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
103                     0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
104                     0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
105             event.setDisplayId(DEFAULT_DISPLAY);
106             events[i] = event;
107             // The order is important here, metaState could be updated and applied to the next key.
108             metaState |= MODIFIER.getOrDefault(keyCode, 0);
109         }
110 
111         for (KeyEvent event: events) {
112             interceptKey(event);
113         }
114 
115         try {
116             Thread.sleep(duration);
117         } catch (InterruptedException e) {
118             throw new RuntimeException(e);
119         }
120 
121         for (int i = count - 1; i >= 0; i--) {
122             final long eventTime = SystemClock.uptimeMillis();
123             final int keyCode = keyCodes[i];
124             final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
125                     0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/,
126                     InputDevice.SOURCE_KEYBOARD);
127             interceptKey(upEvent);
128             metaState &= ~MODIFIER.getOrDefault(keyCode, 0);
129         }
130     }
131 
sendKey(int keyCode)132     void sendKey(int keyCode) {
133         sendKey(keyCode, false);
134     }
135 
sendKey(int keyCode, boolean longPress)136     void sendKey(int keyCode, boolean longPress) {
137         final long downTime = SystemClock.uptimeMillis();
138         final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
139                 0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
140                 0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
141         event.setDisplayId(DEFAULT_DISPLAY);
142         interceptKey(event);
143 
144         if (longPress) {
145             final long nextDownTime = downTime + ViewConfiguration.getLongPressTimeout();
146             final KeyEvent nextDownevent = new KeyEvent(downTime, nextDownTime,
147                     KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, 0 /*metaState*/,
148                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
149                     KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD);
150             interceptKey(nextDownevent);
151         }
152 
153         final long eventTime = longPress
154                 ? SystemClock.uptimeMillis() + ViewConfiguration.getLongPressTimeout()
155                 : SystemClock.uptimeMillis();
156         final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
157                 0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
158                 0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
159         interceptKey(upEvent);
160     }
161 
interceptKey(KeyEvent keyEvent)162     private void interceptKey(KeyEvent keyEvent) {
163         int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent);
164         if ((actions & ACTION_PASS_TO_USER) != 0) {
165             if (0 == mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent)) {
166                 mPhoneWindowManager.dispatchUnhandledKey(keyEvent);
167             }
168         }
169     }
170 }
171