CTS tests for AccessibilityService API to set and access the Soft Keyboard mode.
Change-Id: I63de2e946609b4d320840cad7d48540dacea1aed
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 41e13ee..43a7baf 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -44,23 +44,38 @@
<activity android:label="Full screen activity for gesture dispatch testing"
android:name=".AccessibilityGestureDispatchTest$GestureDispatchActivity"/>
- <service
- android:name="android.accessibilityservice.cts.StubGestureAccessibilityService"
- android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
- <intent-filter>
- <action android:name="android.accessibilityservice.AccessibilityService" />
+ <service
+ android:name="android.accessibilityservice.cts.StubGestureAccessibilityService"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
- <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
- </intent-filter>
+ <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+ </intent-filter>
- <meta-data
- android:name="android.accessibilityservice"
- android:resource="@xml/stub_gesture_a11y_service" />
- </service>
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/stub_gesture_a11y_service" />
+ </service>
+
+ <activity android:label="@string/accessibility_soft_keyboard_modes_activity"
+ android:name=".AccessibilitySoftKeyboardModesTest$SoftKeyboardModesActivity"
+ android:windowSoftInputMode="stateAlwaysVisible" />
+
+ <service android:name=".StubSoftKeyboardModesAccessibilityService"
+ android:label="@string/title_soft_keyboard_modes_accessibility_service"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+ </intent-filter>
+ <meta-data android:name="android.accessibilityservice"
+ android:resource="@xml/stub_soft_keyboard_modes_accessibility_service" />
+ </service>
</application>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.accessibilityservice.cts"
android:label="Tests for the accessibility APIs.">
<meta-data android:name="listener"
diff --git a/tests/accessibilityservice/AndroidTest.xml b/tests/accessibilityservice/AndroidTest.xml
index db2707d..1287524 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -21,4 +21,4 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.accessibilityservice.cts" />
</test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tests/accessibilityservice/res/layout/accessibility_soft_keyboard_modes_test.xml b/tests/accessibilityservice/res/layout/accessibility_soft_keyboard_modes_test.xml
new file mode 100644
index 0000000..577a204
--- /dev/null
+++ b/tests/accessibilityservice/res/layout/accessibility_soft_keyboard_modes_test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/edit_text"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 89ce1df..1c34c2c 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -145,5 +145,16 @@
unveiling\n\n</string>
<string name="full_screen_text_view">Full Screen Text View</string>
+ <!-- Soft Keyboard Modes tests -->
+
+ <!-- String title for the accessibility service -->
+ <string name="accessibility_soft_keyboard_modes_activity">
+ Soft Keyboard Modes Activity</string>
+
+ <string name="title_soft_keyboard_modes_accessibility_service">
+ Stub Soft Keyboard Modes Accessibility Service</string>
+
+ <!-- Description of the accessibility service -->
+ <string name="soft_keyboard_modes_accessibility_service_description">This Accessibility Service was installed for testing purposes. It can be uninstalled by going to Settings > Apps > android.view.accessibilityservice.services and selecting \"Uninstall\".</string>
</resources>
diff --git a/tests/accessibilityservice/res/xml/stub_soft_keyboard_modes_accessibility_service.xml b/tests/accessibilityservice/res/xml/stub_soft_keyboard_modes_accessibility_service.xml
new file mode 100644
index 0000000..b5f9ede
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_soft_keyboard_modes_accessibility_service.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
+ android:canRetrieveWindowContent="true"
+ android:description="@string/soft_keyboard_modes_accessibility_service_description" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
new file mode 100644
index 0000000..649df16
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -0,0 +1,362 @@
+/**
+ * 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.accessibilityservice.cts;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
+import android.app.UiAutomation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.Selection;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import android.accessibilityservice.cts.R;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Test cases for testing the accessibility APIs for interacting with the soft keyboard show mode.
+ */
+public class AccessibilitySoftKeyboardModesTest extends ActivityInstrumentationTestCase2
+ <AccessibilitySoftKeyboardModesTest.SoftKeyboardModesActivity> {
+
+ /**
+ * Timeout in which we are waiting for the system to start the mock
+ * accessibility services.
+ */
+ private static final long TIMEOUT_SERVICE_TOGGLE_MS = 10000;
+
+ private static final long TIMEOUT_PROPAGATE_SETTING = 5000;
+
+ private static final int SHOW_MODE_AUTO = 0;
+ private static final int SHOW_MODE_HIDDEN = 1;
+
+ private int mCallbackCount;
+ private int mLastCallbackValue;
+
+ private Context mContext;
+ private StubSoftKeyboardModesAccessibilityService mService;
+ private SoftKeyboardController mKeyboardController;
+
+ private Object mLock = new Object();
+
+ public AccessibilitySoftKeyboardModesTest() {
+ super(SoftKeyboardModesActivity.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // If we don't call getActivity(), we get an empty list when requesting the number of
+ // windows on screen.
+ getActivity();
+
+ mContext = getInstrumentation().getContext();
+ UiAutomation uiAutomation;
+ try {
+ uiAutomation = getUiAutomation();
+ } catch (RuntimeException e) {
+ // Clean up UI Automation after other tests as we cannot request UI Automation with
+ // different flags if one already exists.
+ uiAutomation = getInstrumentation().getUiAutomation();
+ uiAutomation.destroy();
+
+ // Try to get UI Automation again.
+ uiAutomation = getUiAutomation();
+ }
+ String command = "pm grant " + mContext.getPackageName()
+ + "android.permission.WRITE_SECURE_SETTINGS";
+ executeShellCommand(uiAutomation, command);
+ uiAutomation.destroy();
+
+ disableAllServices();
+ enableTestService();
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ disableAllServices();
+ }
+
+ public void testApiReturnValues_shouldChangeValueOnRequestAndSendCallback() throws Exception {
+ mLastCallbackValue = -1;
+
+ final SoftKeyboardController.OnShowModeChangedListener listener =
+ new SoftKeyboardController.OnShowModeChangedListener() {
+ @Override
+ public void onShowModeChanged(SoftKeyboardController controller, int showMode) {
+ synchronized (mLock) {
+ mLastCallbackValue = showMode;
+ mLock.notifyAll();
+ }
+ }
+ };
+ mKeyboardController.addOnShowModeChangedListener(listener);
+
+ // The soft keyboard should be in its' default mode.
+ assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
+
+ // Set the show mode to SHOW_MODE_HIDDEN.
+ assertTrue(mKeyboardController.setShowMode(SHOW_MODE_HIDDEN));
+
+ // Make sure the mode was changed.
+ assertEquals(SHOW_MODE_HIDDEN, mKeyboardController.getShowMode());
+
+ // Make sure we're getting the callback with the proper value.
+ waitForCallbackValueWithLock(SHOW_MODE_HIDDEN);
+
+ // Make sure we can set the value back to the default.
+ assertTrue(mKeyboardController.setShowMode(SHOW_MODE_AUTO));
+ assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
+ waitForCallbackValueWithLock(SHOW_MODE_AUTO);
+
+ // Make sure we can remove our listener.
+ assertTrue(mKeyboardController.removeOnShowModeChangedListener(listener));
+ }
+
+ public void testHideSoftKeyboard_shouldHideAndShowKeyboardOnRequest() throws Exception {
+ // The soft keyboard should be in its' default mode.
+ assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
+
+ // Note: This Activity always has a visible keyboard (due to windowSoftInputMode being set
+ // to stateAlwaysVisible).
+ int numWindowsWithIme = mService.getTestWindowsListSize();
+
+ // Request the keyboard be hidden.
+ assertTrue(mKeyboardController.setShowMode(SHOW_MODE_HIDDEN));
+ waitForWindowStateChanged();
+
+ // Make sure the keyboard is hidden.
+ assertEquals(numWindowsWithIme - 1, mService.getTestWindowsListSize());
+
+ // Request the default keyboard mode.
+ assertTrue(mKeyboardController.setShowMode(SHOW_MODE_AUTO));
+ waitForWindowStateChanged();
+
+ // Make sure the keyboard is visible.
+ assertEquals(numWindowsWithIme, mService.getTestWindowsListSize());
+ }
+
+
+ public void testHideSoftKeyboard_shouldHideKeyboardUntilAllServicesDisabled() throws Exception {
+ // The soft keyboard should be in its' default mode.
+ assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
+
+ // Note: This Activity always has a visible keyboard (due to windowSoftInputMode being set
+ // to stateAlwaysVisible).
+ int numWindowsWithIme = mService.getTestWindowsListSize();
+
+ // Set the show mode to SHOW_MODE_HIDDEN.
+ assertTrue(mKeyboardController.setShowMode(SHOW_MODE_HIDDEN));
+ waitForWindowStateChanged();
+
+ // Make sure the keyboard is hidden.
+ assertEquals(numWindowsWithIme - 1, mService.getTestWindowsListSize());
+
+ // Make sure we can see the soft keyboard once all Accessibility Services are disabled.
+ disableAllServices();
+ waitForWindowStateChanged();
+
+ // Enable our test service,.
+ enableTestService();
+
+ // See how many windows are present.
+ assertEquals(numWindowsWithIme, mService.getTestWindowsListSize());
+ }
+
+ private synchronized UiAutomation getUiAutomation() {
+ return getInstrumentation()
+ .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ }
+
+ private void executeShellCommand(UiAutomation uiAutomation, String command) throws Exception {
+ ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command);
+ BufferedReader reader = null;
+ try (InputStream inputStream = new FileInputStream(fd.getFileDescriptor())) {
+ reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+ while (reader.readLine() != null) {
+ // Keep reading.
+ }
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ fd.close();
+ }
+ }
+
+ private void waitForCallbackValueWithLock(int expectedValue) throws Exception {
+ long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_PROPAGATE_SETTING;
+
+ while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
+ synchronized(mLock) {
+ if (mLastCallbackValue == expectedValue) {
+ return;
+ }
+ try {
+ mLock.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
+ } catch (InterruptedException e) {
+ // Wait until timeout.
+ }
+ }
+ }
+
+ throw new IllegalStateException("last callback value <" + mLastCallbackValue
+ + "> does not match expected value < " + expectedValue + ">");
+ }
+
+ private void waitForWindowStateChanged() throws Exception {
+ UiAutomation uiAutomation = getUiAutomation();
+ try {
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ // Do nothing.
+ }
+ },
+ new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept (AccessibilityEvent event) {
+ return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
+ }
+ },
+ TIMEOUT_PROPAGATE_SETTING);
+ } catch (TimeoutException ignored) {
+ // Ignore since the event could have occured before this method was called. There should // be a check after this method returns to catch incorrect values.
+ } finally {
+ uiAutomation.destroy();
+ }
+ }
+
+ private void disableAllServices() throws Exception {
+ final Object waitLockForA11yOff = new Object();
+ AccessibilityManager manager =
+ (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ manager.addAccessibilityStateChangeListener(
+ new AccessibilityManager.AccessibilityStateChangeListener() {
+ @Override
+ public void onAccessibilityStateChanged(boolean b) {
+ synchronized (waitLockForA11yOff) {
+ waitLockForA11yOff.notifyAll();
+ }
+ }
+ });
+ ContentResolver cr = mContext.getContentResolver();
+ UiAutomation uiAutomation = getUiAutomation();
+ executeShellCommand(uiAutomation, "settings put secure "
+ + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + " null");
+ uiAutomation.destroy();
+ StubSoftKeyboardModesAccessibilityService.sInstance = null;
+ long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_SERVICE_TOGGLE_MS;
+ while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
+ synchronized (waitLockForA11yOff) {
+ if (!manager.isEnabled()) {
+ return;
+ }
+ try {
+ waitLockForA11yOff.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
+ } catch (InterruptedException e) {
+ // Ignored; loop again
+ }
+ }
+ }
+ throw new RuntimeException("Unable to turn accessibility off");
+ }
+
+ private void enableTestService() throws Exception {
+ Context context = getInstrumentation().getContext();
+ AccessibilityManager manager =
+ (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ List<AccessibilityServiceInfo> serviceInfos =
+ manager.getInstalledAccessibilityServiceList();
+ for (int i = 0; i < serviceInfos.size(); i++) {
+ AccessibilityServiceInfo serviceInfo = serviceInfos.get(i);
+ if (context.getString(R.string.soft_keyboard_modes_accessibility_service_description)
+ .equals(serviceInfo.getDescription())) {
+ ContentResolver cr = context.getContentResolver();
+ UiAutomation uiAutomation = getUiAutomation();
+ String command = "settings put secure "
+ + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + " "
+ + serviceInfo.getId();
+ executeShellCommand(uiAutomation, command);
+ executeShellCommand(uiAutomation, "settings put secure "
+ + Settings.Secure.ACCESSIBILITY_ENABLED + " 1");
+ uiAutomation.destroy();
+
+ // We have enabled the services of interest and need to wait until they
+ // are instantiated and started (if needed) and the system binds to them.
+ long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_SERVICE_TOGGLE_MS;
+ while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
+ synchronized(
+ StubSoftKeyboardModesAccessibilityService.sWaitObjectForConnecting) {
+ if (StubSoftKeyboardModesAccessibilityService.sInstance != null) {
+ mService = StubSoftKeyboardModesAccessibilityService.sInstance;
+ mKeyboardController = mService.getTestSoftKeyboardController();
+ return;
+ }
+ try {
+ StubSoftKeyboardModesAccessibilityService.sWaitObjectForConnecting.wait(
+ timeoutTimeMillis - SystemClock.uptimeMillis());
+ } catch (InterruptedException e) {
+ // Ignored; loop again
+ }
+ }
+ }
+ throw new IllegalStateException("Stub accessibility service not started");
+ }
+ }
+ throw new IllegalStateException("Stub accessiblity service not found");
+ }
+
+ /**
+ * Activity for testing the AccessibilityService API for hiding and showring the soft keyboard.
+ */
+ public static class SoftKeyboardModesActivity extends AccessibilityTestActivity {
+ public SoftKeyboardModesActivity() {
+ super();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.accessibility_soft_keyboard_modes_test);
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubSoftKeyboardModesAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubSoftKeyboardModesAccessibilityService.java
new file mode 100644
index 0000000..27d51d9
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubSoftKeyboardModesAccessibilityService.java
@@ -0,0 +1,64 @@
+/*
+ * 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.accessibilityservice.cts;
+
+import android.accessibilityservice.AccessibilityService;
+import android.content.Intent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+/**
+ * Stub accessibility service for testing APIs to show/hide Soft Keyboard.
+ */
+public class StubSoftKeyboardModesAccessibilityService extends AccessibilityService {
+
+ public static Object sWaitObjectForConnecting = new Object();
+
+ public static StubSoftKeyboardModesAccessibilityService sInstance = null;
+
+ @Override
+ protected void onServiceConnected() {
+ synchronized (sWaitObjectForConnecting) {
+ sInstance = this;
+ sWaitObjectForConnecting.notifyAll();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ sInstance = null;
+ super.onDestroy();
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onInterrupt() {
+ /* do nothing */
+ }
+
+ public SoftKeyboardController getTestSoftKeyboardController() {
+ return sInstance.getSoftKeyboardController();
+ }
+
+ public int getTestWindowsListSize() {
+ return sInstance.getWindows().size();
+ }
+}