1 /* 2 * Copyright (C) 2016 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 com.android.compatibility.common.util; 18 19 import android.app.Instrumentation; 20 import android.os.Looper; 21 import android.os.SystemClock; 22 import android.util.Log; 23 import android.view.InputDevice; 24 import android.view.KeyCharacterMap; 25 import android.view.KeyEvent; 26 import android.view.View; 27 import android.view.inputmethod.InputMethodManager; 28 29 import java.lang.reflect.Field; 30 31 /** 32 * Utility class to send KeyEvents bypassing the IME. The code is similar to functions in 33 * {@link Instrumentation} and {@link android.test.InstrumentationTestCase} classes. It uses 34 * {@link InputMethodManager#dispatchKeyEventFromInputMethod(View, KeyEvent)} to send the events. 35 * After sending the events waits for idle. 36 */ 37 public final class CtsKeyEventUtil { 38 CtsKeyEventUtil()39 private CtsKeyEventUtil() {} 40 41 /** 42 * Sends the key events corresponding to the text to the app being instrumented. 43 * 44 * @param instrumentation the instrumentation used to run the test. 45 * @param targetView View to find the ViewRootImpl and dispatch. 46 * @param text The text to be sent. Null value returns immediately. 47 */ sendString(final Instrumentation instrumentation, final View targetView, final String text)48 public static void sendString(final Instrumentation instrumentation, final View targetView, 49 final String text) { 50 if (text == null) { 51 return; 52 } 53 54 KeyEvent[] events = getKeyEvents(text); 55 56 if (events != null) { 57 for (int i = 0; i < events.length; i++) { 58 // We have to change the time of an event before injecting it because 59 // all KeyEvents returned by KeyCharacterMap.getEvents() have the same 60 // time stamp and the system rejects too old events. Hence, it is 61 // possible for an event to become stale before it is injected if it 62 // takes too long to inject the preceding ones. 63 sendKey(instrumentation, targetView, KeyEvent.changeTimeRepeat( 64 events[i], SystemClock.uptimeMillis(), 0 /* newRepeat */)); 65 } 66 } 67 } 68 69 /** 70 * Sends a series of key events through instrumentation. For instance: 71 * sendKeys(view, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER). 72 * 73 * @param instrumentation the instrumentation used to run the test. 74 * @param targetView View to find the ViewRootImpl and dispatch. 75 * @param keys The series of key codes. 76 */ sendKeys(final Instrumentation instrumentation, final View targetView, final int...keys)77 public static void sendKeys(final Instrumentation instrumentation, final View targetView, 78 final int...keys) { 79 final int count = keys.length; 80 81 for (int i = 0; i < count; i++) { 82 try { 83 sendKeyDownUp(instrumentation, targetView, keys[i]); 84 } catch (SecurityException e) { 85 // Ignore security exceptions that are now thrown 86 // when trying to send to another app, to retain 87 // compatibility with existing tests. 88 } 89 } 90 } 91 92 /** 93 * Sends a series of key events through instrumentation. The sequence of keys is a string 94 * containing the key names as specified in KeyEvent, without the KEYCODE_ prefix. For 95 * instance: sendKeys(view, "DPAD_LEFT A B C DPAD_CENTER"). Each key can be repeated by using 96 * the N* prefix. For instance, to send two KEYCODE_DPAD_LEFT, use the following: 97 * sendKeys(view, "2*DPAD_LEFT"). 98 * 99 * @param instrumentation the instrumentation used to run the test. 100 * @param targetView View to find the ViewRootImpl and dispatch. 101 * @param keysSequence The sequence of keys. 102 */ sendKeys(final Instrumentation instrumentation, final View targetView, final String keysSequence)103 public static void sendKeys(final Instrumentation instrumentation, final View targetView, 104 final String keysSequence) { 105 final String[] keys = keysSequence.split(" "); 106 final int count = keys.length; 107 108 for (int i = 0; i < count; i++) { 109 String key = keys[i]; 110 int repeater = key.indexOf('*'); 111 112 int keyCount; 113 try { 114 keyCount = repeater == -1 ? 1 : Integer.parseInt(key.substring(0, repeater)); 115 } catch (NumberFormatException e) { 116 Log.w("ActivityTestCase", "Invalid repeat count: " + key); 117 continue; 118 } 119 120 if (repeater != -1) { 121 key = key.substring(repeater + 1); 122 } 123 124 for (int j = 0; j < keyCount; j++) { 125 try { 126 final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key); 127 final int keyCode = keyCodeField.getInt(null); 128 try { 129 sendKeyDownUp(instrumentation, targetView, keyCode); 130 } catch (SecurityException e) { 131 // Ignore security exceptions that are now thrown 132 // when trying to send to another app, to retain 133 // compatibility with existing tests. 134 } 135 } catch (NoSuchFieldException e) { 136 Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key); 137 break; 138 } catch (IllegalAccessException e) { 139 Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key); 140 break; 141 } 142 } 143 } 144 } 145 146 /** 147 * Sends an up and down key events. 148 * 149 * @param instrumentation the instrumentation used to run the test. 150 * @param targetView View to find the ViewRootImpl and dispatch. 151 * @param key The integer keycode for the event to be sent. 152 */ sendKeyDownUp(final Instrumentation instrumentation, final View targetView, final int key)153 public static void sendKeyDownUp(final Instrumentation instrumentation, final View targetView, 154 final int key) { 155 sendKey(instrumentation, targetView, new KeyEvent(KeyEvent.ACTION_DOWN, key), 156 false /* waitForIdle */); 157 sendKey(instrumentation, targetView, new KeyEvent(KeyEvent.ACTION_UP, key)); 158 } 159 160 /** 161 * Sends a key event. 162 * 163 * @param instrumentation the instrumentation used to run the test. 164 * @param targetView View to find the ViewRootImpl and dispatch. 165 * @param event KeyEvent to be send. 166 */ sendKey(final Instrumentation instrumentation, final View targetView, final KeyEvent event)167 public static void sendKey(final Instrumentation instrumentation, final View targetView, 168 final KeyEvent event) { 169 sendKey(instrumentation, targetView, event, true /* waitForIdle */); 170 } 171 sendKey(final Instrumentation instrumentation, final View targetView, final KeyEvent event, boolean waitForIdle)172 private static void sendKey(final Instrumentation instrumentation, final View targetView, 173 final KeyEvent event, boolean waitForIdle) { 174 validateNotAppThread(); 175 176 long downTime = event.getDownTime(); 177 long eventTime = event.getEventTime(); 178 int action = event.getAction(); 179 int code = event.getKeyCode(); 180 int repeatCount = event.getRepeatCount(); 181 int metaState = event.getMetaState(); 182 int deviceId = event.getDeviceId(); 183 int scanCode = event.getScanCode(); 184 int source = event.getSource(); 185 int flags = event.getFlags(); 186 if (source == InputDevice.SOURCE_UNKNOWN) { 187 source = InputDevice.SOURCE_KEYBOARD; 188 } 189 if (eventTime == 0) { 190 eventTime = SystemClock.uptimeMillis(); 191 } 192 if (downTime == 0) { 193 downTime = eventTime; 194 } 195 196 final KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, 197 metaState, deviceId, scanCode, flags, source); 198 199 InputMethodManager imm = targetView.getContext().getSystemService(InputMethodManager.class); 200 imm.dispatchKeyEventFromInputMethod(imm.isActive() ? null : targetView, newEvent); 201 if (waitForIdle) { 202 instrumentation.waitForIdleSync(); 203 } 204 } 205 206 /** 207 * Sends a key event while holding another modifier key down, then releases both keys and 208 * waits for idle sync. Useful for sending combinations like shift + tab. 209 * 210 * @param instrumentation the instrumentation used to run the test. 211 * @param targetView View to find the ViewRootImpl and dispatch. 212 * @param keyCodeToSend The integer keycode for the event to be sent. 213 * @param modifierKeyCodeToHold The integer keycode of the modifier to be held. 214 */ sendKeyWhileHoldingModifier(final Instrumentation instrumentation, final View targetView, final int keyCodeToSend, final int modifierKeyCodeToHold)215 public static void sendKeyWhileHoldingModifier(final Instrumentation instrumentation, 216 final View targetView, final int keyCodeToSend, 217 final int modifierKeyCodeToHold) { 218 final int metaState = getMetaStateForModifierKeyCode(modifierKeyCodeToHold); 219 final long downTime = SystemClock.uptimeMillis(); 220 221 final KeyEvent holdKeyDown = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, 222 modifierKeyCodeToHold, 0 /* repeat */); 223 sendKey(instrumentation ,targetView, holdKeyDown); 224 225 final KeyEvent keyDown = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, 226 keyCodeToSend, 0 /* repeat */, metaState); 227 sendKey(instrumentation, targetView, keyDown); 228 229 final KeyEvent keyUp = new KeyEvent(downTime, downTime, KeyEvent.ACTION_UP, 230 keyCodeToSend, 0 /* repeat */, metaState); 231 sendKey(instrumentation, targetView, keyUp); 232 233 final KeyEvent holdKeyUp = new KeyEvent(downTime, downTime, KeyEvent.ACTION_UP, 234 modifierKeyCodeToHold, 0 /* repeat */); 235 sendKey(instrumentation, targetView, holdKeyUp); 236 237 instrumentation.waitForIdleSync(); 238 } 239 getMetaStateForModifierKeyCode(int modifierKeyCode)240 private static int getMetaStateForModifierKeyCode(int modifierKeyCode) { 241 if (!KeyEvent.isModifierKey(modifierKeyCode)) { 242 throw new IllegalArgumentException("Modifier key expected, but got: " 243 + KeyEvent.keyCodeToString(modifierKeyCode)); 244 } 245 246 int metaState; 247 switch (modifierKeyCode) { 248 case KeyEvent.KEYCODE_SHIFT_LEFT: 249 metaState = KeyEvent.META_SHIFT_LEFT_ON; 250 break; 251 case KeyEvent.KEYCODE_SHIFT_RIGHT: 252 metaState = KeyEvent.META_SHIFT_RIGHT_ON; 253 break; 254 case KeyEvent.KEYCODE_ALT_LEFT: 255 metaState = KeyEvent.META_ALT_LEFT_ON; 256 break; 257 case KeyEvent.KEYCODE_ALT_RIGHT: 258 metaState = KeyEvent.META_ALT_RIGHT_ON; 259 break; 260 case KeyEvent.KEYCODE_CTRL_LEFT: 261 metaState = KeyEvent.META_CTRL_LEFT_ON; 262 break; 263 case KeyEvent.KEYCODE_CTRL_RIGHT: 264 metaState = KeyEvent.META_CTRL_RIGHT_ON; 265 break; 266 case KeyEvent.KEYCODE_META_LEFT: 267 metaState = KeyEvent.META_META_LEFT_ON; 268 break; 269 case KeyEvent.KEYCODE_META_RIGHT: 270 metaState = KeyEvent.META_META_RIGHT_ON; 271 break; 272 case KeyEvent.KEYCODE_SYM: 273 metaState = KeyEvent.META_SYM_ON; 274 break; 275 case KeyEvent.KEYCODE_NUM: 276 metaState = KeyEvent.META_NUM_LOCK_ON; 277 break; 278 case KeyEvent.KEYCODE_FUNCTION: 279 metaState = KeyEvent.META_FUNCTION_ON; 280 break; 281 default: 282 // Safety net: all modifier keys need to have at least one meta state associated. 283 throw new UnsupportedOperationException("No meta state associated with " 284 + "modifier key: " + KeyEvent.keyCodeToString(modifierKeyCode)); 285 } 286 287 return KeyEvent.normalizeMetaState(metaState); 288 } 289 getKeyEvents(final String text)290 private static KeyEvent[] getKeyEvents(final String text) { 291 KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); 292 return keyCharacterMap.getEvents(text.toCharArray()); 293 } 294 validateNotAppThread()295 private static void validateNotAppThread() { 296 if (Looper.myLooper() == Looper.getMainLooper()) { 297 throw new RuntimeException( 298 "This method can not be called from the main application thread"); 299 } 300 } 301 } 302