• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package android.view.cts.input;
18 
19 import static android.view.InputDevice.SOURCE_KEYBOARD;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertNotNull;
23 
24 import android.Manifest;
25 import android.app.Instrumentation;
26 import android.content.Context;
27 import android.content.pm.PackageManager;
28 import android.content.res.Resources;
29 import android.platform.test.annotations.AppModeSdkSandbox;
30 import android.view.KeyEvent;
31 import android.view.WindowManager;
32 import android.view.cts.R;
33 
34 import androidx.test.ext.junit.runners.AndroidJUnit4;
35 import androidx.test.filters.MediumTest;
36 import androidx.test.platform.app.InstrumentationRegistry;
37 import androidx.test.rule.ActivityTestRule;
38 
39 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
40 import com.android.compatibility.common.util.WindowUtil;
41 import com.android.cts.input.ConfigurationItem;
42 import com.android.cts.input.InputJsonParser;
43 import com.android.cts.input.UinputDevice;
44 import com.android.cts.input.UinputRegisterCommand;
45 
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.Rule;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 
52 import java.util.Arrays;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57 
58 /**
59  * CTS test case for generic.kl key layout mapping.
60  * This test utilize uinput command line tool to create a test device, and configure the virtual
61  * device to have all keys need to be tested. The JSON format input for device configuration
62  * and EV_KEY injection will be created directly from this test for uinput command.
63  * Keep res/raw/Generic.kl in sync with framework/base/data/keyboards/Generic.kl, this file
64  * will be loaded and parsed in this test, looping through all key labels and the corresponding
65  * EV_KEY code, injecting the KEY_UP and KEY_DOWN event to uinput, then verify the KeyEvent
66  * delivered to test application view. Except meta control keys and special keys not delivered
67  * to apps, all key codes in generic.kl will be verified.
68  *
69  */
70 @MediumTest
71 @RunWith(AndroidJUnit4.class)
72 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
73 public class InputDeviceKeyLayoutMapTest {
74     private static final String TAG = "InputDeviceKeyLayoutMapTest";
75     private static final String LABEL_PREFIX = "KEYCODE_";
76     private static final int DEVICE_ID = 1;
77     private static final int EV_SYN = 0;
78     private static final int EV_KEY = 1;
79     private static final int EV_KEY_DOWN = 1;
80     private static final int EV_KEY_UP = 0;
81     private static final int GOOGLE_VENDOR_ID = 0x18d1;
82     private static final int GOOGLE_VIRTUAL_KEYBOARD_ID = 0x001f;
83     private static final int POLL_EVENT_TIMEOUT_SECONDS = 5;
84 
85     private static final Set<String> EXCLUDED_KEYS = new HashSet<>(Arrays.asList(
86                 // Meta control keys.
87                 "META_LEFT",
88                 "META_RIGHT",
89                 // KeyEvents not delivered to apps.
90                 "APP_SWITCH",
91                 "ASSIST",
92                 "BACK",
93                 "BRIGHTNESS_DOWN",
94                 "BRIGHTNESS_UP",
95                 "EMOJI_PICKER",
96                 "HOME",
97                 "KEYBOARD_BACKLIGHT_DOWN",
98                 "KEYBOARD_BACKLIGHT_TOGGLE",
99                 "KEYBOARD_BACKLIGHT_UP",
100                 "LANGUAGE_SWITCH",
101                 "MACRO_1",
102                 "MACRO_2",
103                 "MACRO_3",
104                 "MACRO_4",
105                 "MUTE",
106                 "NOTIFICATION",
107                 "POWER",
108                 "RECENT_APPS",
109                 "SCREENSHOT",
110                 "SEARCH",
111                 "SLEEP",
112                 "SOFT_SLEEP",
113                 "STYLUS_BUTTON_TERTIARY",
114                 "STYLUS_BUTTON_PRIMARY",
115                 "STYLUS_BUTTON_SECONDARY",
116                 "SYSRQ",
117                 "WAKEUP",
118                 "VOICE_ASSIST",
119                 // Keys that cause the test activity to lose focus
120                 "CALCULATOR",
121                 "CALENDAR",
122                 "CONTACTS",
123                 "ENVELOPE",
124                 "EXPLORER",
125                 "MUSIC"
126                 ));
127 
128     private Map<String, Integer> mKeyLayout;
129     private Instrumentation mInstrumentation;
130     private UinputDevice mUinputDevice;
131     private InputJsonParser mParser;
132     private WindowManager mWindowManager;
133     private boolean mIsLeanback;
134     private boolean mVolumeKeysHandledInWindowManager;
135 
nativeLoadKeyLayout(String genericKeyLayout)136     private static native Map<String, Integer> nativeLoadKeyLayout(String genericKeyLayout);
137 
138     static {
139         System.loadLibrary("ctsview_jni");
140     }
141 
142     @Rule(order = 0)
143     public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
144             androidx.test.platform.app.InstrumentationRegistry
145                     .getInstrumentation().getUiAutomation(),
146             Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
147 
148     @Rule(order = 1)
149     public ActivityTestRule<InputDeviceKeyLayoutMapTestActivity> mActivityRule =
150             new ActivityTestRule<>(InputDeviceKeyLayoutMapTestActivity.class);
151 
152     @Before
setup()153     public void setup() {
154         mInstrumentation = InstrumentationRegistry.getInstrumentation();
155         WindowUtil.waitForFocus(mActivityRule.getActivity());
156         Context context = mInstrumentation.getTargetContext();
157         mParser = new InputJsonParser(context);
158         mWindowManager = context.getSystemService(WindowManager.class);
159         mIsLeanback = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
160         mVolumeKeysHandledInWindowManager = context.getResources().getBoolean(
161                 Resources.getSystem().getIdentifier("config_handleVolumeKeysInWindowManager",
162                         "bool", "android"));
163         mKeyLayout = nativeLoadKeyLayout(mParser.readRegisterCommand(R.raw.Generic));
164         mUinputDevice = new UinputDevice(
165                 mInstrumentation, SOURCE_KEYBOARD, createDeviceRegisterCommand());
166     }
167 
168     @After
tearDown()169     public void tearDown() {
170         if (mUinputDevice != null) {
171             mUinputDevice.close();
172         }
173     }
174 
175     /**
176      * Get a KeyEvent from event queue or timeout.
177      *
178      * @return KeyEvent delivered to test activity, null if timeout.
179      */
getKeyEvent()180     private KeyEvent getKeyEvent() {
181         return mActivityRule.getActivity().getKeyEvent(POLL_EVENT_TIMEOUT_SECONDS);
182     }
183 
assertReceivedKeyEvent(int action, int keyCode)184     private void assertReceivedKeyEvent(int action, int keyCode) {
185         KeyEvent receivedKeyEvent = getKeyEvent();
186         assertNotNull("Did not receive " + KeyEvent.keyCodeToString(keyCode), receivedKeyEvent);
187         assertEquals(action, receivedKeyEvent.getAction());
188         assertEquals(keyCode, receivedKeyEvent.getKeyCode());
189     }
190 
createDeviceRegisterCommand()191     private UinputRegisterCommand createDeviceRegisterCommand() {
192         List<ConfigurationItem> configurationItems = Arrays.asList(
193                 new ConfigurationItem("UI_SET_EVBIT", List.of("EV_KEY")),
194                 new ConfigurationItem("UI_SET_KEYBIT", mKeyLayout.values().stream().toList())
195         );
196 
197         return new UinputRegisterCommand(
198                 DEVICE_ID,
199                 "Virtual All Buttons Device (Test)",
200                 GOOGLE_VENDOR_ID,
201                 GOOGLE_VIRTUAL_KEYBOARD_ID,
202                 "bluetooth",
203                 "bluetooth:1",
204                 configurationItems,
205                 Map.of(),
206                 /* ffEffectsMax= */ null
207         );
208     }
209 
210     /**
211      * Simulate pressing a key.
212      * @param evKeyCode The key scan code
213      */
pressKey(int evKeyCode)214     private void pressKey(int evKeyCode) {
215         int[] evCodesDown = new int[] {
216                 EV_KEY, evKeyCode, EV_KEY_DOWN,
217                 EV_SYN, 0, 0};
218         mUinputDevice.injectEvents(Arrays.toString(evCodesDown));
219 
220         int[] evCodesUp = new int[] {
221                 EV_KEY, evKeyCode, EV_KEY_UP,
222                 EV_SYN, 0, 0 };
223         mUinputDevice.injectEvents(Arrays.toString(evCodesUp));
224     }
225 
226     /**
227      * Whether one key code is a volume key code.
228      * @param keyCode The key code
229      */
isVolumeKey(int keyCode)230     private static boolean isVolumeKey(int keyCode) {
231         return keyCode == KeyEvent.KEYCODE_VOLUME_UP
232                 || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
233                 || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE;
234     }
235 
236     /**
237      * Whether one key code should be forwarded to apps.
238      * @param keyCode The key code
239      */
isForwardedToApps(int keyCode)240     private boolean isForwardedToApps(int keyCode) {
241         if (mWindowManager.isGlobalKey(keyCode)) {
242             return false;
243         }
244         if (isVolumeKey(keyCode) && (mIsLeanback || mVolumeKeysHandledInWindowManager)) {
245             return false;
246         }
247         return true;
248     }
249 
250     @Test
testLayoutKeyEvents()251     public void testLayoutKeyEvents() {
252         for (Map.Entry<String, Integer> entry : mKeyLayout.entrySet()) {
253             if (EXCLUDED_KEYS.contains(entry.getKey())) {
254                 continue;
255             }
256 
257             String label = LABEL_PREFIX + entry.getKey();
258             final int evKey = entry.getValue();
259             final int keyCode = KeyEvent.keyCodeFromString(label);
260 
261             if (!isForwardedToApps(keyCode)) {
262                 continue;
263             }
264 
265             pressKey(evKey);
266             assertReceivedKeyEvent(KeyEvent.ACTION_DOWN, keyCode);
267             assertReceivedKeyEvent(KeyEvent.ACTION_UP, keyCode);
268         }
269     }
270 
271 }
272