diff options
author | Daniel Xie <dxie@google.com> | 2016-06-24 20:28:20 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2016-06-24 20:28:20 +0000 |
commit | 3a87160af28de950df7931e132d40ff9b0c6dbd8 (patch) | |
tree | 65a1171b430c073489f3e0e588e2e17637a50f58 | |
parent | 057b4181495f6ce20d0644bebbe7b961f1e8b31b (diff) | |
parent | 0461b5760cf7c4dd04748eb45edd82d6f827d280 (diff) | |
download | platform_cts-3a87160af28de950df7931e132d40ff9b0c6dbd8.tar.gz platform_cts-3a87160af28de950df7931e132d40ff9b0c6dbd8.tar.bz2 platform_cts-3a87160af28de950df7931e132d40ff9b0c6dbd8.zip |
Merge "DO NOT MERGE Update TextViewTest to use KeyEventUtil for KeyEvents" into marshmallow-cts-dev
-rw-r--r-- | libs/deviceutil/src/android/cts/util/KeyEventUtil.java | 197 | ||||
-rw-r--r-- | tests/tests/widget/AndroidManifest.xml | 3 | ||||
-rw-r--r-- | tests/tests/widget/src/android/widget/cts/TextViewTest.java | 58 |
3 files changed, 230 insertions, 28 deletions
diff --git a/libs/deviceutil/src/android/cts/util/KeyEventUtil.java b/libs/deviceutil/src/android/cts/util/KeyEventUtil.java new file mode 100644 index 00000000000..961337d3321 --- /dev/null +++ b/libs/deviceutil/src/android/cts/util/KeyEventUtil.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.cts.util; + +import android.app.Instrumentation; +import android.os.Looper; +import android.os.SystemClock; +import android.util.Log; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import java.lang.reflect.Field; + +/** + * Utility class to send KeyEvents to TextView bypassing the IME. The code is similar to functions + * in {@link Instrumentation} and {@link android.test.InstrumentationTestCase} classes. It uses + * {@link View#dispatchKeyEvent(KeyEvent)} to send the events. + * After sending the events waits for idle. + */ +public class KeyEventUtil { + private final Instrumentation mInstrumentation; + + public KeyEventUtil(Instrumentation instrumentation) { + this.mInstrumentation = instrumentation; + } + + /** + * Sends the key events corresponding to the text to the app being instrumented. + * + * @param targetView View to find the ViewRootImpl and dispatch. + * @param text The text to be sent. Null value returns immediately. + */ + public final void sendString(final View targetView, final String text) { + if (text == null) { + return; + } + + KeyEvent[] events = getKeyEvents(text); + + if (events != null) { + for (int i = 0; i < events.length; i++) { + // We have to change the time of an event before injecting it because + // all KeyEvents returned by KeyCharacterMap.getEvents() have the same + // time stamp and the system rejects too old events. Hence, it is + // possible for an event to become stale before it is injected if it + // takes too long to inject the preceding ones. + sendKey(targetView, KeyEvent.changeTimeRepeat(events[i], SystemClock.uptimeMillis(), + 0)); + } + } + } + + /** + * Sends a series of key events through instrumentation. For instance: + * sendKeys(view, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER). + * + * @param targetView View to find the ViewRootImpl and dispatch. + * @param keys The series of key codes. + */ + public final void sendKeys(final View targetView, final int...keys) { + final int count = keys.length; + + for (int i = 0; i < count; i++) { + try { + sendKeyDownUp(targetView, keys[i]); + } catch (SecurityException e) { + // Ignore security exceptions that are now thrown + // when trying to send to another app, to retain + // compatibility with existing tests. + } + } + } + + /** + * Sends a series of key events through instrumentation. The sequence of keys is a string + * containing the key names as specified in KeyEvent, without the KEYCODE_ prefix. For + * instance: sendKeys(view, "DPAD_LEFT A B C DPAD_CENTER"). Each key can be repeated by using + * the N* prefix. For instance, to send two KEYCODE_DPAD_LEFT, use the following: + * sendKeys(view, "2*DPAD_LEFT"). + * + * @param targetView View to find the ViewRootImpl and dispatch. + * @param keysSequence The sequence of keys. + */ + public final void sendKeys(final View targetView, final String keysSequence) { + final String[] keys = keysSequence.split(" "); + final int count = keys.length; + + for (int i = 0; i < count; i++) { + String key = keys[i]; + int repeater = key.indexOf('*'); + + int keyCount; + try { + keyCount = repeater == -1 ? 1 : Integer.parseInt(key.substring(0, repeater)); + } catch (NumberFormatException e) { + Log.w("ActivityTestCase", "Invalid repeat count: " + key); + continue; + } + + if (repeater != -1) { + key = key.substring(repeater + 1); + } + + for (int j = 0; j < keyCount; j++) { + try { + final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key); + final int keyCode = keyCodeField.getInt(null); + try { + sendKeyDownUp(targetView, keyCode); + } catch (SecurityException e) { + // Ignore security exceptions that are now thrown + // when trying to send to another app, to retain + // compatibility with existing tests. + } + } catch (NoSuchFieldException e) { + Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key); + break; + } catch (IllegalAccessException e) { + Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key); + break; + } + } + } + } + + /** + * Sends an up and down key events. + * + * @param targetView View to find the ViewRootImpl and dispatch. + * @param key The integer keycode for the event to be send. + */ + public final void sendKeyDownUp(final View targetView, final int key) { + sendKey(targetView, new KeyEvent(KeyEvent.ACTION_DOWN, key)); + sendKey(targetView, new KeyEvent(KeyEvent.ACTION_UP, key)); + } + + /** + * Sends a key event. + * + * @param targetView View to find the ViewRootImpl and dispatch. + * @param event KeyEvent to be send. + */ + public final void sendKey(final View targetView, final KeyEvent event) { + long downTime = event.getDownTime(); + long eventTime = event.getEventTime(); + int action = event.getAction(); + int code = event.getKeyCode(); + int repeatCount = event.getRepeatCount(); + int metaState = event.getMetaState(); + int deviceId = event.getDeviceId(); + int scancode = event.getScanCode(); + int source = event.getSource(); + int flags = event.getFlags(); + if (source == InputDevice.SOURCE_UNKNOWN) { + source = InputDevice.SOURCE_KEYBOARD; + } + if (eventTime == 0) { + eventTime = SystemClock.uptimeMillis(); + } + if (downTime == 0) { + downTime = eventTime; + } + + final KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, + metaState, deviceId, scancode, flags, source); + + mInstrumentation.runOnMainSync(new Runnable() { + @Override + public void run() { + targetView.dispatchKeyEvent(newEvent); + } + }); + mInstrumentation.waitForIdleSync(); + } + + private KeyEvent[] getKeyEvents(final String text) { + KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + return keyCharacterMap.getEvents(text.toCharArray()); + } +} diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml index bc431061909..46cdd4de6c1 100644 --- a/tests/tests/widget/AndroidManifest.xml +++ b/tests/tests/widget/AndroidManifest.xml @@ -213,7 +213,8 @@ </activity> <activity android:name="android.widget.cts.TextViewCtsActivity" - android:label="TextViewCtsActivity"> + android:label="TextViewCtsActivity" + android:windowSoftInputMode="stateAlwaysHidden"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java index 12818d38052..a4c27f47897 100644 --- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java +++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Resources.NotFoundException; +import android.cts.util.KeyEventUtil; import android.cts.util.PollingCheck; import android.cts.util.WidgetTestUtils; import android.graphics.Bitmap; @@ -119,6 +120,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc + "this text, I would love to see the kind of devices you guys now use!"; private static final long TIMEOUT = 5000; private CharSequence mTransformedText; + private KeyEventUtil mKeyEventUtil; public TextViewTest() { super("com.android.cts.widget", TextViewCtsActivity.class); @@ -135,6 +137,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc } }.run(); mInstrumentation = getInstrumentation(); + mKeyEventUtil = new KeyEventUtil(mInstrumentation); } /** @@ -257,7 +260,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc assertSame(movementMethod, mTextView.getMovementMethod()); assertEquals(selectionStart, Selection.getSelectionStart(mTextView.getText())); assertEquals(selectionEnd, Selection.getSelectionEnd(mTextView.getText())); - sendKeys(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_ALT_LEFT, + mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_DPAD_UP); // the selection has been removed. assertEquals(selectionStart, Selection.getSelectionStart(mTextView.getText())); @@ -276,7 +279,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc assertNull(mTextView.getMovementMethod()); assertEquals(selectionStart, Selection.getSelectionStart(mTextView.getText())); assertEquals(selectionEnd, Selection.getSelectionEnd(mTextView.getText())); - sendKeys(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_ALT_LEFT, + mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_DPAD_UP); // the selection will not be changed. assertEquals(selectionStart, Selection.getSelectionStart(mTextView.getText())); @@ -1270,7 +1273,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc initTextViewForTyping(); // Type some text. - mInstrumentation.sendStringSync("abc"); + mKeyEventUtil.sendString(mTextView, "abc"); mActivity.runOnUiThread(new Runnable() { public void run() { // Precondition: The cursor is at the end of the text. @@ -1302,8 +1305,9 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc initTextViewForTyping(); // Simulate deleting text and undoing it. - mInstrumentation.sendStringSync("xyz"); - sendKeys(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL); + mKeyEventUtil.sendString(mTextView, "xyz"); + mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, KeyEvent + .KEYCODE_DEL); mActivity.runOnUiThread(new Runnable() { public void run() { // Precondition: The text was actually deleted. @@ -1433,8 +1437,8 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc initTextViewForTyping(); // Create two undo operations, an insert and a delete. - mInstrumentation.sendStringSync("xyz"); - sendKeys(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL); + mKeyEventUtil.sendString(mTextView, "xyz"); + mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL); mActivity.runOnUiThread(new Runnable() { public void run() { // Calling setText() clears both undo operations, so undo doesn't happen. @@ -1455,7 +1459,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc initTextViewForTyping(); // Type some text. This creates an undo entry. - mInstrumentation.sendStringSync("abc"); + mKeyEventUtil.sendString(mTextView, "abc"); mActivity.runOnUiThread(new Runnable() { public void run() { // Undo the typing to create a redo entry. @@ -1474,7 +1478,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc initTextViewForTyping(); // Type some text. - mInstrumentation.sendStringSync("abc"); + mKeyEventUtil.sendString(mTextView, "abc"); mActivity.runOnUiThread(new Runnable() { public void run() { // Programmatically append some text. @@ -1497,7 +1501,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc initTextViewForTyping(); // Type some text. - mInstrumentation.sendStringSync("abc"); + mKeyEventUtil.sendString(mTextView, "abc"); mActivity.runOnUiThread(new Runnable() { public void run() { // Directly modify the underlying Editable to insert some text. @@ -1546,7 +1550,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc mTextView.addTextChangedListener(new ConvertToSpacesTextWatcher()); // Type some text. - mInstrumentation.sendStringSync("abc"); + mKeyEventUtil.sendString(mTextView, "abc"); mActivity.runOnUiThread(new Runnable() { public void run() { // TextWatcher altered the text. @@ -1584,7 +1588,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc initTextViewForTyping(); // Type some text. - mInstrumentation.sendStringSync("abc"); + mKeyEventUtil.sendString(mTextView, "abc"); mActivity.runOnUiThread(new Runnable() { public void run() { // Pressing Control-Z triggers undo. @@ -1607,7 +1611,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc initTextViewForTyping(); // Type some text to create an undo operation. - mInstrumentation.sendStringSync("abc"); + mKeyEventUtil.sendString(mTextView, "abc"); mActivity.runOnUiThread(new Runnable() { public void run() { // Parcel and unparcel the TextView. @@ -1618,7 +1622,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc mInstrumentation.waitForIdleSync(); // Delete a character to create a new undo operation. - sendKeys(KeyEvent.KEYCODE_DEL); + mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL); mActivity.runOnUiThread(new Runnable() { public void run() { assertEquals("ab", mTextView.getText().toString()); @@ -1645,8 +1649,8 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc initTextViewForTyping(); // Type and delete to create two new undo operations. - mInstrumentation.sendStringSync("a"); - sendKeys(KeyEvent.KEYCODE_DEL); + mKeyEventUtil.sendString(mTextView, "a"); + mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL); mActivity.runOnUiThread(new Runnable() { public void run() { // Empty the undo stack then parcel and unparcel the TextView. While the undo @@ -1660,8 +1664,8 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc mInstrumentation.waitForIdleSync(); // Create two more undo operations. - mInstrumentation.sendStringSync("b"); - sendKeys(KeyEvent.KEYCODE_DEL); + mKeyEventUtil.sendString(mTextView, "b"); + mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL); mActivity.runOnUiThread(new Runnable() { public void run() { // Verify undo still works. @@ -1896,7 +1900,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc assertEquals(errorText, mTextView.getError().toString()); - mInstrumentation.sendStringSync("a"); + mKeyEventUtil.sendString(mTextView, "a"); // a key event that will not change the TextView's text assertEquals("", mTextView.getText().toString()); // The icon and error message will not be reset to null @@ -1912,7 +1916,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc }); mInstrumentation.waitForIdleSync(); - mInstrumentation.sendStringSync("1"); + mKeyEventUtil.sendString(mTextView, "1"); // a key event cause changes to the TextView's text assertEquals("1", mTextView.getText().toString()); // the error message and icon will be cleared. @@ -1938,13 +1942,13 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc assertSame(expected, mTextView.getFilters()); - mInstrumentation.sendStringSync("a"); + mKeyEventUtil.sendString(mTextView, "a"); // the text is capitalized by InputFilter.AllCaps assertEquals("A", mTextView.getText().toString()); - mInstrumentation.sendStringSync("b"); + mKeyEventUtil.sendString(mTextView, "b"); // the text is capitalized by InputFilter.AllCaps assertEquals("AB", mTextView.getText().toString()); - mInstrumentation.sendStringSync("c"); + mKeyEventUtil.sendString(mTextView, "c"); // 'C' could not be accepted, because there is a length filter. assertEquals("AB", mTextView.getText().toString()); @@ -2141,11 +2145,11 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc public void testPressKey() { initTextViewForTyping(); - mInstrumentation.sendStringSync("a"); + mKeyEventUtil.sendString(mTextView, "a"); assertEquals("a", mTextView.getText().toString()); - mInstrumentation.sendStringSync("b"); + mKeyEventUtil.sendString(mTextView, "b"); assertEquals("ab", mTextView.getText().toString()); - sendKeys(KeyEvent.KEYCODE_DEL); + mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL); assertEquals("a", mTextView.getText().toString()); } @@ -2457,7 +2461,7 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewCtsAc assertSame(PasswordTransformationMethod.getInstance(), mTextView.getTransformationMethod()); - sendKeys("H E 2*L O"); + mKeyEventUtil.sendKeys(mTextView, "H E 2*L O"); mActivity.runOnUiThread(new Runnable() { public void run() { mTextView.append(" "); |