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();
+    }
+}