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