Adding CTS tests for the new accessibility APIs in ICS.
1. Added tests for the new APIs introduced in ICS.
2. Fixed a failing CTS test.
NOTE: This change does *not* affect the system image, rather CTS.
Change-Id: I15eabeb1f60404707f99ac2048455463fce4c0c8
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 29164bc..81758f6 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -28,6 +28,8 @@
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
+ <meta-data android:name="android.accessibilityservice"
+ android:resource="@xml/accessibilityservice" />
</service>
<service android:name=".DelegatingAccessibilityService$DelegatingConnectionService"
diff --git a/tests/accessibilityservice/res/xml/accessibilityservice.xml b/tests/accessibilityservice/res/xml/accessibilityservice.xml
new file mode 100644
index 0000000..31a3f71
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/accessibilityservice.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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"
+ android:canRetrieveWindowContent="true" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl b/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
index b5ebf19..868bd72 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
+++ b/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
@@ -20,7 +20,7 @@
/**
* Interface for interacting with the accessibility service mock.
*/
-interface IAccessibilityServiceDelegate {
+oneway interface IAccessibilityServiceDelegate {
/**
* Delegate an {@link android.view.accessibility.AccessibilityEvent}.
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
index 37a9c54..5680b7c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
@@ -17,7 +17,6 @@
package android.accessibilityservice.delegate;
import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceDelegate;
import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
import android.app.Service;
@@ -64,14 +63,6 @@
@Override
protected void onServiceConnected() {
- AccessibilityServiceInfo info = new AccessibilityServiceInfo();
- info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
- info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
- info.packageNames = new String[] {
- "com.android.cts.accessibilityservice"
- };
- setServiceInfo(info);
-
// the service is ready to be used only
// after the system has bound to it
sServiceDelegate = this;
diff --git a/tests/tests/accessibilityservice/AndroidManifest.xml b/tests/tests/accessibilityservice/AndroidManifest.xml
index 70e1092..c75ffb2 100644
--- a/tests/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/tests/accessibilityservice/AndroidManifest.xml
@@ -26,6 +26,9 @@
<activity android:label="@string/accessibility_end_to_end_test_activity"
android:name="android.accessibilityservice.cts.AccessibilityEndToEndTestActivity"/>
+ <activity android:label="@string/accessibility_query_window_test_activity"
+ android:name="android.accessibilityservice.cts.AccessibilityWindowQueryActivity"/>
+
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/tests/tests/accessibilityservice/res/layout/query_window_test.xml b/tests/tests/accessibilityservice/res/layout/query_window_test.xml
new file mode 100644
index 0000000..4b32eb1
--- /dev/null
+++ b/tests/tests/accessibilityservice/res/layout/query_window_test.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2011, 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ >
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button1"
+ />
+ <Button
+ android:id="@+id/button2"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button2"
+ />
+ <Button
+ android:id="@+id/button3"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button3"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:id="@+id/button4"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button4"
+ />
+ <Button
+ android:id="@+id/button5"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button5"
+ />
+ <Button
+ android:id="@+id/button6"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button6"
+ android:contentDescription="@string/contentDescription"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:id="@+id/button7"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button7"
+ />
+ <Button
+ android:id="@+id/button8"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button8"
+ />
+ <Button
+ android:id="@+id/button9"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button9"
+ />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/tests/accessibilityservice/res/values/strings.xml b/tests/tests/accessibilityservice/res/values/strings.xml
index e86d3cc..7b3cd0f 100644
--- a/tests/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/tests/accessibilityservice/res/values/strings.xml
@@ -44,4 +44,38 @@
<!-- String notification message -->
<string name="notification_message">Notification message</string>
+
+ <!-- String title of the accessibility query window test activity -->
+ <string name="accessibility_query_window_test_activity">Query window test</string>
+
+ <!-- String Button1 text -->
+ <string name="button1">Button1</string>
+
+ <!-- String Button2 text -->
+ <string name="button2">Button2</string>
+
+ <!-- String Button3 text -->
+ <string name="button3">Button3</string>
+
+ <!-- String Button4 text -->
+ <string name="button4">Button4</string>
+
+ <!-- String Button5 text -->
+ <string name="button5">Button5</string>
+
+ <!-- String Button6 text -->
+ <string name="button6">Button6</string>
+
+ <!-- String with content description for Button6 -->
+ <string name="contentDescription">contentDescription</string>
+
+ <!-- String Button7 text -->
+ <string name="button7">Button7</string>
+
+ <!-- String Button8 text -->
+ <string name="button8">Button8</string>
+
+ <!-- String Button9 text -->
+ <string name="button9">Button9</string>
+
</resources>
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl b/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
index b5ebf19..868bd72 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
@@ -20,7 +20,7 @@
/**
* Interface for interacting with the accessibility service mock.
*/
-interface IAccessibilityServiceDelegate {
+oneway interface IAccessibilityServiceDelegate {
/**
* Delegate an {@link android.view.accessibility.AccessibilityEvent}.
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDelegateHelper.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDelegateHelper.java
new file mode 100644
index 0000000..1e7c625
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDelegateHelper.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2011 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 static junit.framework.Assert.fail;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceDelegate;
+import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.List;
+
+/**
+ * This class is a helper for implementing delegation of accessibility events.
+ * This is required because changing accessibility settings can be performed
+ * either via writing to the secure settings, which a CTS test cannot do, or
+ * by manual interaction from the settings application. However, manually
+ * enabling the testing accessibility service does not work because the test
+ * runner restarts the package before running the tests, thus breaking the
+ * bond between the system and the manually enabled testing service. So,
+ * we have a delegating service that is manually enabled which services as a
+ * proxy to deliver accessibility events to the testing service.
+ */
+class AccessibilityDelegateHelper implements ServiceConnection {
+
+ /**
+ * Timeout required for pending Binder calls or event processing to
+ * complete.
+ */
+ public static final long TIMEOUT_ASYNC_PROCESSING = 500;
+
+ /**
+ * The package of the accessibility service mock interface.
+ */
+ private static final String DELEGATING_SERVICE_PACKAGE =
+ "android.accessibilityservice.delegate";
+
+ /**
+ * The package of the delegating accessibility service interface.
+ */
+ private static final String DELEGATING_SERVICE_CLASS_NAME =
+ "android.accessibilityservice.delegate.DelegatingAccessibilityService";
+
+ /**
+ * The package of the delegating accessibility service connection interface.
+ */
+ private static final String DELEGATING_SERVICE_CONNECTION_CLASS_NAME =
+ "android.accessibilityservice.delegate."
+ + "DelegatingAccessibilityService$DelegatingConnectionService";
+
+ /**
+ * The client accessibility service to which to delegate.
+ */
+ private final AccessibilityService mAccessibilityService;
+
+ /**
+ * Lock for synchronization.
+ */
+ private final Object mLock = new Object();
+
+ /**
+ * Whether this delegate is initialized.
+ */
+ private boolean mInitialized;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param service The service to which to delegate.
+ */
+ public AccessibilityDelegateHelper(AccessibilityService service) {
+ mAccessibilityService = service;
+ }
+
+ /**
+ * Ensures the required setup for the test performed and that it is bound to the
+ * DelegatingAccessibilityService which runs in another process. The setup is
+ * enabling accessibility and installing and enabling the delegating accessibility
+ * service this test binds to.
+ * </p>
+ * Note: Please look at the class description for information why such an
+ * approach is taken.
+ */
+ public void bindToDelegatingAccessibilityService(Context context) {
+ // check if accessibility is enabled
+ AccessibilityManager accessibilityManager = (AccessibilityManager) context
+ .getSystemService(Service.ACCESSIBILITY_SERVICE);
+
+ if (!accessibilityManager.isEnabled()) {
+ throw new IllegalStateException("Delegating service not enabled. "
+ + "(Settings -> Accessibility -> Delegating Accessibility Service)");
+ }
+
+ // check if the delegating service is running
+ List<AccessibilityServiceInfo> enabledServices =
+ accessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ boolean delegatingServiceRunning = false;
+ for (AccessibilityServiceInfo enabledService : enabledServices) {
+ ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
+ if (DELEGATING_SERVICE_PACKAGE.equals(serviceInfo.packageName)
+ && DELEGATING_SERVICE_CLASS_NAME.equals(serviceInfo.name)) {
+ delegatingServiceRunning = true;
+ break;
+ }
+ }
+
+ if (!delegatingServiceRunning) {
+ // delegating service not running, so check if it is installed at all
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ packageManager.getServiceInfo(new ComponentName(DELEGATING_SERVICE_PACKAGE,
+ DELEGATING_SERVICE_CLASS_NAME), 0);
+ } catch (NameNotFoundException nnfe) {
+ throw new IllegalStateException("CtsDelegatingAccessibilityService.apk" +
+ " not installed.");
+ }
+
+ throw new IllegalStateException("Delegating Accessibility Service not running."
+ + "(Settings -> Accessibility -> Delegating Accessibility Service)");
+ }
+
+ Intent intent = new Intent().setClassName(DELEGATING_SERVICE_PACKAGE,
+ DELEGATING_SERVICE_CONNECTION_CLASS_NAME);
+ context.bindService(intent, this, Context.BIND_AUTO_CREATE);
+
+ final long beginTime = SystemClock.uptimeMillis();
+ synchronized (mLock) {
+ while (true) {
+ if (mInitialized) {
+ return;
+ }
+ final long elapsedTime = (SystemClock.uptimeMillis() - beginTime);
+ final long remainingTime = TIMEOUT_ASYNC_PROCESSING - elapsedTime;
+ if (remainingTime <= 0) {
+ if (!mInitialized) {
+ throw new IllegalStateException("Cound not connect to the delegating"
+ + " accessibility service");
+ }
+ return;
+ }
+ try {
+ mLock.wait(remainingTime);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc ServiceConnection#onServiceConnected(ComponentName,IBinder)}
+ */
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ IAccessibilityServiceDelegateConnection connection =
+ IAccessibilityServiceDelegateConnection.Stub.asInterface(service);
+ try {
+ connection.setAccessibilityServiceDelegate(new IAccessibilityServiceDelegate.Stub() {
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ mAccessibilityService.onAccessibilityEvent(event);
+ }
+ @Override
+ public void onInterrupt() {
+ mAccessibilityService.onInterrupt();
+ }
+ });
+ mInitialized = true;
+ synchronized (mLock) {
+ mLock.notifyAll();
+ }
+ } catch (RemoteException re) {
+ fail("Could not set delegate to the delegating service.");
+ }
+ }
+
+ /**
+ * {@inheritDoc ServiceConnection#onServiceDisconnected(ComponentName)}
+ */
+ public void onServiceDisconnected(ComponentName name) {
+ mInitialized = false;
+ /* do nothing */
+ }
+}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index af6d37d..5a565ec 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,40 +16,25 @@
package android.accessibilityservice.cts;
-import com.android.cts.accessibilityservice.R;
-
import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.IAccessibilityServiceDelegate;
-import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
-import android.app.ActivityManager.RunningServiceInfo;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
+import com.android.cts.accessibilityservice.R;
+
import junit.framework.TestCase;
import java.util.Iterator;
@@ -62,18 +47,22 @@
* creating an {@link Activity} and poking around so {@link AccessibilityEvent}s
* are generated and their correct dispatch verified.
* <p>
- * Note: The end-to-end test is composed of two APKs, one with a mock accessibility
- * service, another with the instrumented activity and test cases. The motivation for
- * two APKs design is that CTS tests cannot access the secure settings which is
- * required for enabling accessibility and accessibility services. Therefore, manual
- * installation of the <strong>CtsAccessibilityServiceTestMockService.apk</strong>
+ * Note: The accessibility CTS tests are composed of two APKs, one with delegating
+ * accessibility service and another with the instrumented activity and test cases.
+ * The motivation for two APKs design is that CTS tests cannot access the secure
+ * settings which is required for enabling accessibility services, hence there is
+ * no way to manipulate accessibility settings programmaticaly. Further, manually
+ * enabling an accessibility service in the tests APK will not work either because
+ * the instrumentation restarts the process under test which would break the binding
+ * between the accessibility service and the system.
+ * <p>
+ * Therefore, manual installation of the
+ * <strong>CtsAccessibilityServiceTestMockService.apk</strong>
* whose source is located at <strong>cts/tests/accessibility</strong> is required.
- * Once the former package has been installed accessibility must be enabled (Settings ->
- * Accessibility), the mock service must be enabled (Settings -> Accessibility
- * -> Mock Accessibility Service), and then the CTS tests in this package can be
- * successfully run. Further, the mock and tests run in separate processes since
- * the instrumentation restarts the process in which it is running and this
- * breaks the binding between the mock accessibility service and the system.
+ * Once the former package has been installed the service must be enabled
+ * (Settings -> Accessibility -> Delegating Accessibility Service), and then the CTS tests
+ * in this package can be successfully run.
+ * </p>
*/
public class AccessibilityEndToEndTest extends
ActivityInstrumentationTestCase2<AccessibilityEndToEndTestActivity> {
@@ -82,35 +71,10 @@
* Timeout required for pending Binder calls or event processing to
* complete.
*/
- private static final long MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING = 500;
+ private static final long TIMEOUT_ASYNC_PROCESSING = 500;
/**
- * The count of the polling attempts during {@link #MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING}
- */
- private static final long COUNT_POLLING_ATTEMPTS = 10;
-
- /**
- * The package of the accessibility service mock interface.
- */
- private static final String DELEGATING_SERVICE_PACKAGE =
- "android.accessibilityservice.delegate";
-
- /**
- * The package of the delegating accessibility service interface.
- */
- private static final String DELEGATING_SERVICE_CLASS_NAME =
- "android.accessibilityservice.delegate.DelegatingAccessibilityService";
-
- /**
- * The package of the delegating accessibility service connection interface.
- */
- private static final String DELEGATING_SERVICE_CONNECTION_CLASS_NAME =
- "android.accessibilityservice.delegate."
- + "DelegatingAccessibilityService$DelegatingConnectionService";
-
- /**
- * Creates a new instance for testing
- * {@link AccessibilityEndToEndTestActivity}.
+ * Creates a new instance for testing {@link AccessibilityEndToEndTestActivity}.
*
* @throws Exception If any error occurs.
*/
@@ -124,7 +88,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
// create and populate the expected event
AccessibilityEvent selectedEvent = AccessibilityEvent.obtain();
@@ -135,7 +99,7 @@
selectedEvent.setItemCount(2);
selectedEvent.setCurrentItemIndex(1);
selectedEvent.setEnabled(true);
- selectedEvent.setScrollable(true);
+ selectedEvent.setScrollable(false);
selectedEvent.setFromIndex(0);
selectedEvent.setToIndex(1);
@@ -153,7 +117,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -162,7 +126,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
// create and populate the expected event
AccessibilityEvent clickedEvent = AccessibilityEvent.obtain();
@@ -186,7 +150,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -195,7 +159,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
// create and populate the expected event
AccessibilityEvent longClickedEvent = AccessibilityEvent.obtain();
@@ -219,7 +183,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -228,7 +192,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
// create and populate the expected event
AccessibilityEvent focusedEvent = AccessibilityEvent.obtain();
@@ -254,7 +218,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -270,7 +234,7 @@
});
// wait for the generated focus event to be dispatched
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
final String beforeText = activity.getString(R.string.text_input_blah);
@@ -302,7 +266,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -311,7 +275,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
String title = activity.getString(R.string.alert_title);
String message = activity.getString(R.string.alert_message);
@@ -342,7 +306,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -351,7 +315,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
String message = activity.getString(R.string.notification_message);
@@ -383,61 +347,18 @@
notificationManager.notify(notificationId, notification);
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
// remove the notification
notificationManager.cancel(notificationId);
}
- /**
- * Asserts the the mock accessibility service has been successfully verified
- * (which is it has received the expected method calls with expected
- * arguments) within the {@link #MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING}. The
- * verified state is checked by polling upon small intervals.
- *
- * @param service The service to verify.
- * @throws Exception If the verification has failed with exception after the
- * {@link #MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING}.
- */
- private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service)
- throws Throwable {
- Throwable lastVerifyThrowable = null;
- long beginTime = SystemClock.uptimeMillis();
- long pollTmeout = MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING / COUNT_POLLING_ATTEMPTS;
+ static class MockAccessibilityService extends AccessibilityService {
- // poll until the timeout has elapsed
- while (SystemClock.uptimeMillis() - beginTime < MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING) {
- // sleep first since immediate call will always fail
- try {
- Thread.sleep(pollTmeout);
- } catch (InterruptedException ie) {
- /* ignore */
- }
-
- try {
- service.verify();
- // success - reset so it is not accept more events
- service.reset();
- return;
- } catch (IllegalStateException ise) {
- // this exception is thrown if the expected event is not
- // received yet, so we will keep trying within the timeout
- lastVerifyThrowable = ise;
- continue;
- } catch (Throwable t) {
- // we have just failed
- lastVerifyThrowable = t;
- break;
- }
- }
-
- // failure - reset so it is not accept more events
- service.reset();
- throw lastVerifyThrowable;
- }
-
- static class MockAccessibilityService extends AccessibilityService implements
- ServiceConnection {
+ /**
+ * Helper for connecting to the delegating accessibility service.
+ */
+ private final AccessibilityDelegateHelper mAccessibilityDelegateHelper;
/**
* The singleton instance.
@@ -451,6 +372,11 @@
new LinkedList<AccessibilityEvent>();
/**
+ * Reusable temporary builder.
+ */
+ private final StringBuilder mTempBuilder = new StringBuilder();
+
+ /**
* Interruption call this service expects to receive.
*/
private boolean mExpectedInterrupt;
@@ -461,14 +387,9 @@
private boolean mReplaying;
/**
- * Flag indicating if this mock is initialized.
+ * Lock for synchronization.
*/
- private boolean mInitialized;
-
- /**
- * The {@link Context} whose services to utilize.
- */
- private Context mContext;
+ private final Object mLock = new Object();
/**
* Gets the {@link MockAccessibilityService} singleton.
@@ -491,100 +412,57 @@
* Creates a new instance.
*/
private MockAccessibilityService(Context context) {
- mContext = context;
- ensureSetupAndBoundToDelegatingAccessibilityService();
- }
-
- /**
- * Ensures the required setup for the test performed and that it is bound to the
- * DelegatingAccessibilityService which runs in another process. The setup is
- * enabling accessibility and installing and enabling the delegating accessibility
- * service this test binds to.
- * </p>
- * Note: Please look at the class description for information why such an
- * approach is taken.
- */
- public void ensureSetupAndBoundToDelegatingAccessibilityService() {
- // check if accessibility is enabled
- AccessibilityManager accessibilityManager = (AccessibilityManager) mContext
- .getSystemService(Service.ACCESSIBILITY_SERVICE);
-
- if (!accessibilityManager.isEnabled()) {
- throw new IllegalStateException("Accessibility not enabled. "
- + "(Settings -> Accessibility)");
- }
-
- // check if the delegating service is running
- ComponentName delegatingServiceName = new ComponentName(
- DELEGATING_SERVICE_PACKAGE, DELEGATING_SERVICE_CLASS_NAME);
- ActivityManager activityManager = (ActivityManager) mContext
- .getSystemService(Service.ACTIVITY_SERVICE);
- boolean delegatingServiceRunning = false;
-
- for (RunningServiceInfo runningServiceInfo : activityManager.getRunningServices(100)) {
- if (delegatingServiceName.equals(runningServiceInfo.service)) {
- delegatingServiceRunning = true;
- break;
- }
- }
-
- if (!delegatingServiceRunning) {
- // delegating service not running, so check if it is installed at all
- try {
- PackageManager packageManager = mContext.getPackageManager();
- packageManager.getServiceInfo(delegatingServiceName, 0);
- } catch (NameNotFoundException nnfe) {
- throw new IllegalStateException("CtsDelegatingAccessibilityService.apk" +
- " not installed.");
- }
-
- throw new IllegalStateException("Delegating Accessibility Service not running."
- + "(Settings -> Accessibility -> Delegating Accessibility Service)");
- }
-
- Intent intent = new Intent().setClassName(DELEGATING_SERVICE_PACKAGE,
- DELEGATING_SERVICE_CONNECTION_CLASS_NAME);
- mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
-
- long beginTime = SystemClock.uptimeMillis();
- long pollTmeout = MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING / COUNT_POLLING_ATTEMPTS;
-
- // bind to the delegating service which runs in another process by
- // polling until the binder connection is established
- while (SystemClock.uptimeMillis() - beginTime < MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING) {
- if (mInitialized) {
- // success
- return;
- }
- try {
- Thread.sleep(pollTmeout);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
+ mAccessibilityDelegateHelper = new AccessibilityDelegateHelper(this);
+ mAccessibilityDelegateHelper.bindToDelegatingAccessibilityService(
+ context);
}
/**
* Starts replaying the mock.
*/
- private void replay() {
+ public void replay() {
mReplaying = true;
}
/**
- * Verifies if all expected service methods have been called.
+ * Verifies the mock service.
+ *
+ * @throws IllegalStateException If the verification has failed.
*/
- private void verify() {
- synchronized (this) {
- if (!mReplaying) {
- throw new IllegalStateException("Did you forget to call replay()");
- }
- if (mExpectedInterrupt) {
- throw new IllegalStateException("Expected call to #interrupt() not received");
- }
- if (!mExpectedEvents.isEmpty()) {
- throw new IllegalStateException("Expected a call to onAccessibilityEvent() for "
- + "events \"" + mExpectedEvents + "\" not received");
+ public void verify() throws IllegalStateException {
+ StringBuilder problems = mTempBuilder;
+ final long startTime = SystemClock.uptimeMillis();
+ synchronized (mLock) {
+ while (true) {
+ if (!mReplaying) {
+ throw new IllegalStateException("Did you forget to call replay()?");
+ }
+ if (!mExpectedInterrupt && mExpectedEvents.isEmpty()) {
+ reset();
+ return; // success
+ }
+ problems.setLength(0);
+ if (mExpectedInterrupt) {
+ problems.append("Expected call to #interrupt() not received.");
+ }
+ if (!mExpectedEvents.isEmpty()) {
+ problems.append("Expected a call to onAccessibilityEvent() for events \""
+ + mExpectedEvents + "\" not received.");
+ }
+ final long elapsedTime = SystemClock.uptimeMillis() - startTime;
+ final long remainingTime = TIMEOUT_ASYNC_PROCESSING - elapsedTime;
+ if (remainingTime <= 0) {
+ reset();
+ if (problems.length() > 0) {
+ throw new IllegalStateException(problems.toString());
+ }
+ return;
+ }
+ try {
+ mLock.wait(remainingTime);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
}
}
}
@@ -593,10 +471,11 @@
* Resets this instance so it can be reused.
*/
private void reset() {
- synchronized (this) {
+ synchronized (mLock) {
mExpectedEvents.clear();
mExpectedInterrupt = false;
mReplaying = false;
+ mLock.notifyAll();
}
}
@@ -620,7 +499,7 @@
@Override
public void onAccessibilityEvent(AccessibilityEvent receivedEvent) {
- synchronized (this) {
+ synchronized (mLock) {
if (!mReplaying) {
return;
}
@@ -629,48 +508,25 @@
}
AccessibilityEvent expectedEvent = mExpectedEvents.poll();
assertEqualsAccessiblityEvent(expectedEvent, receivedEvent);
+ mLock.notifyAll();
}
}
@Override
public void onInterrupt() {
- synchronized (this) {
+ synchronized (mLock) {
if (!mReplaying) {
return;
}
-
if (!mExpectedInterrupt) {
throw new IllegalStateException("Unexpected call to onInterrupt()");
}
-
mExpectedInterrupt = false;
+ mLock.notifyAll();
}
}
/**
- * {@inheritDoc ServiceConnection#onServiceConnected(ComponentName,IBinder)}
- */
- public void onServiceConnected(ComponentName name, IBinder service) {
- IAccessibilityServiceDelegateConnection connection =
- IAccessibilityServiceDelegateConnection.Stub
- .asInterface(service);
- try {
- connection.setAccessibilityServiceDelegate(new AccessibilityServiceDelegate(this));
- mInitialized = true;
- } catch (RemoteException re) {
- fail("Could not set delegate to the delegating service.");
- }
- }
-
- /**
- * {@inheritDoc ServiceConnection#onServiceDisconnected(ComponentName)}
- */
- public void onServiceDisconnected(ComponentName name) {
- mInitialized = false;
- /* do nothing */
- }
-
- /**
* Compares all properties of the <code>expectedEvent</code> and the
* <code>receviedEvent</code> to verify that the received event is the
* one that is expected.
@@ -764,84 +620,5 @@
receivedTextIterator.next().toString());
}
}
-
- /**
- * This class is the delegate called by the DelegatingAccessibilityService.
- */
- private class AccessibilityServiceDelegate extends
- IAccessibilityServiceDelegate.Stub implements Handler.Callback {
-
- /**
- * Tag for logging.
- */
- private static final String LOG_TAG = "AccessibilityServiceDelegate";
-
- /**
- * Message type for calling {@link #onInterrupt()}
- */
- private static final int DO_ON_INTERRUPT = 10;
-
- /**
- * Message type for calling {@link #onAccessibilityEvent(AccessibilityEvent)}
- */
- private static final int DO_ON_ACCESSIBILITY_EVENT = 20;
-
- /**
- * Caller for handling {@link Message}s
- */
- private final Handler mHandler;
-
- /**
- * The {@link MockAccessibilityService} to which to delegate;
- */
- private MockAccessibilityService mMockAccessibilityService;
-
- /**
- * Creates a new instance.
- *
- * @param mockAccessibilityService The service to whcih to delegate.
- */
- public AccessibilityServiceDelegate(MockAccessibilityService mockAccessibilityService) {
- mMockAccessibilityService = mockAccessibilityService;
- mHandler = new Handler(this);
- }
-
- /**
- * {@inheritDoc IAccessibilityServiceDelegate#onAccessibilityEvent(AccessibilityEvent)}
- */
- public void onAccessibilityEvent(AccessibilityEvent event) {
- Message message = Message.obtain(mHandler, DO_ON_ACCESSIBILITY_EVENT, event);
- mHandler.sendMessage(message);
- }
-
- /**
- * {@inheritDoc IAccessibilityServiceDelegate#onInterrupt()}
- */
- public void onInterrupt() {
- Message message = mHandler.obtainMessage(DO_ON_INTERRUPT);
- mHandler.sendMessage(message);
- }
-
- /**
- * {@inheritDoc Handler.Callback#handleMessage(Message)}
- */
- public boolean handleMessage(Message message) {
- switch (message.what) {
- case DO_ON_ACCESSIBILITY_EVENT:
- AccessibilityEvent event = (AccessibilityEvent) message.obj;
- if (event != null) {
- mMockAccessibilityService.onAccessibilityEvent(event);
- event.recycle();
- }
- return true;
- case DO_ON_INTERRUPT:
- mMockAccessibilityService.onInterrupt();
- return true;
- default:
- Log.w(LOG_TAG, "Unknown message type " + message.what);
- return false;
- }
- }
- }
}
}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
new file mode 100644
index 0000000..6f72a75
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2011 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.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.cts.accessibilityservice.R;
+
+/**
+ * Activity for testing the accessibility APIs for querying of
+ * the screen content. These APIs allow exploring the screen and
+ * requesting an action to be performed on a given view from an
+ * AccessiiblityService.
+ */
+public class AccessibilityWindowQueryActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.query_window_test);
+
+ findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ /* do nothing */
+ }
+ });
+ findViewById(R.id.button5).setOnLongClickListener(new View.OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ return true;
+ }
+ });
+ }
+}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java
new file mode 100644
index 0000000..d798052
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java
@@ -0,0 +1,497 @@
+/**
+ * Copyright (C) 2011 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 static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
+
+import android.accessibilityservice.AccessibilityService;
+import android.content.Context;
+import android.graphics.Rect;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.cts.accessibilityservice.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Activity for testing the accessibility APIs for querying of
+ * the screen content. These APIs allow exploring the screen and
+ * requesting an action to be performed on a given view from an
+ * AccessiiblityService.
+ * <p>
+ * Note: The accessibility CTS tests are composed of two APKs, one with delegating
+ * accessibility service and another with the instrumented activity and test cases.
+ * The motivation for two APKs design is that CTS tests cannot access the secure
+ * settings which is required for enabling accessibility services, hence there is
+ * no way to manipulate accessibility settings programmaticaly. Further, manually
+ * enabling an accessibility service in the tests APK will not work either because
+ * the instrumentation restarts the process under test which would break the binding
+ * between the accessibility service and the system.
+ * <p>
+ * Therefore, manual installation of the
+ * <strong>CtsAccessibilityServiceTestMockService.apk</strong>
+ * whose source is located at <strong>cts/tests/accessibility</strong> is required.
+ * Once the former package has been installed the service must be enabled
+ * (Settings -> Accessibility -> Delegating Accessibility Service), and then the CTS tests
+ * in this package can be successfully run.
+ * </p>
+ */
+public class AccessibilityWindowQueryActivityTest
+ extends ActivityInstrumentationTestCase2<AccessibilityWindowQueryActivity> {
+
+ private interface AccessibilityEventFilter {
+ public boolean accept(AccessibilityEvent event);
+ }
+
+ public AccessibilityWindowQueryActivityTest() {
+ super(AccessibilityWindowQueryActivity.class);
+ }
+
+ @Override
+ public void setUp() {
+ // start the activity and wait for a handle to its window root.
+ startActivityAndWaitForFirstEvent();
+ }
+
+ @LargeTest
+ public void testFindByText() throws Exception {
+ // find a view by text
+ List<AccessibilityNodeInfo> buttons = findAccessibilityNodeInfosByText(
+ getAwaitedAccessibilityEventSource(), "butto");
+ assertEquals(9, buttons.size());
+ }
+
+ @LargeTest
+ public void testFindByContentDescription() throws Exception {
+ // find a view by text
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.contentDescription);
+ assertNotNull(button);
+ }
+
+ @LargeTest
+ public void testTraverseWindow() throws Exception {
+ // make list of expected nodes
+ List<String> classNameAndTextList = new ArrayList<String>();
+ classNameAndTextList.add("com.android.internal.policy.impl.PhoneWindow$DecorView");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.FrameLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.ButtonButton1");
+ classNameAndTextList.add("android.widget.ButtonButton2");
+ classNameAndTextList.add("android.widget.ButtonButton3");
+ classNameAndTextList.add("android.widget.ButtonButton4");
+ classNameAndTextList.add("android.widget.ButtonButton5");
+ classNameAndTextList.add("android.widget.ButtonButton6");
+ classNameAndTextList.add("android.widget.ButtonButton7");
+ classNameAndTextList.add("android.widget.ButtonButton8");
+ classNameAndTextList.add("android.widget.ButtonButton9");
+
+ Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+ fringe.add(getAwaitedAccessibilityEventSource());
+
+ // do a BFS traversal and check nodes
+ while (!fringe.isEmpty()) {
+ AccessibilityNodeInfo current = fringe.poll();
+
+ CharSequence text = current.getText();
+ String receivedClassNameAndText = current.getClassName().toString()
+ + ((text != null) ? text.toString() : "");
+ String expectedClassNameAndText = classNameAndTextList.remove(0);
+
+ assertEquals("Did not get the expected node info",
+ expectedClassNameAndText, receivedClassNameAndText);
+
+ final int childCount = current.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ AccessibilityNodeInfo child = current.getChild(i);
+ fringe.add(child);
+ }
+ }
+ }
+
+ @LargeTest
+ public void testPerformActionFocus() throws Exception {
+ // find a view and make sure it is not focused
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isFocused());
+
+ // focus the view
+ assertTrue(button.performAction(ACTION_FOCUS));
+
+ // find the view again and make sure it is focused
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertTrue(button.isFocused());
+ }
+
+ @LargeTest
+ public void testPerformActionClearFocus() throws Exception {
+ // find a view and make sure it is not focused
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isFocused());
+
+ // focus the view
+ assertTrue(button.performAction(ACTION_FOCUS));
+
+ // find the view again and make sure it is focused
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertTrue(button.isFocused());
+
+ // unfocus the view
+ assertTrue(button.performAction(ACTION_CLEAR_FOCUS));
+
+ // find the view again and make sure it is not focused
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertFalse(button.isFocused());
+ }
+
+ @LargeTest
+ public void testPerformActionSelect() throws Exception {
+ // find a view and make sure it is not selected
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isSelected());
+
+ // select the view
+ assertTrue(button.performAction(ACTION_SELECT));
+
+ // find the view again and make sure it is selected
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertTrue(button.isSelected());
+ }
+
+ @LargeTest
+ public void testPerformActionClearSelection() throws Exception {
+ // find a view and make sure it is not selected
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isSelected());
+
+ // select the view
+ assertTrue(button.performAction(ACTION_SELECT));
+
+ // find the view again and make sure it is selected
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),R
+ .string.button5);
+
+ assertTrue(button.isSelected());
+
+ // unselect the view
+ assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
+
+ // find the view again and make sure it is not selected
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertFalse(button.isSelected());
+ }
+
+ @LargeTest
+ public void testGetEventSource() throws Exception {
+ // find a view and make sure it is not focused
+ final AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isSelected());
+
+ // focus and wait for the event
+ AccessibilityQueryBridge bridge = AccessibilityQueryBridge.getInstance(
+ getInstrumentation().getContext());
+ bridge.perfromActionAndWaitForEvent(
+ new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(button.performAction(ACTION_FOCUS));
+ }
+ },
+ new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+ });
+
+ // check that last event source
+ AccessibilityNodeInfo source = getAwaitedAccessibilityEventSource();
+ assertNotNull(source);
+
+ // bounds
+ Rect buttonBounds = new Rect();
+ button.getBoundsInParent(buttonBounds);
+ Rect sourceBounds = new Rect();
+ source.getBoundsInParent(sourceBounds);
+
+ assertEquals(buttonBounds.left, sourceBounds.left);
+ assertEquals(buttonBounds.right, sourceBounds.right);
+ assertEquals(buttonBounds.top, sourceBounds.top);
+ assertEquals(buttonBounds.bottom, sourceBounds.bottom);
+
+ // char sequence attributes
+ assertEquals(button.getPackageName(), source.getPackageName());
+ assertEquals(button.getClassName(), source.getClassName());
+ assertEquals(button.getText(), source.getText());
+ assertSame(button.getContentDescription(), source.getContentDescription());
+
+ // boolean attributes
+ assertSame(button.isFocusable(), source.isFocusable());
+ assertSame(button.isClickable(), source.isClickable());
+ assertSame(button.isEnabled(), source.isEnabled());
+ assertNotSame(button.isFocused(), source.isFocused());
+ assertSame(button.isLongClickable(), source.isLongClickable());
+ assertSame(button.isPassword(), source.isPassword());
+ assertSame(button.isSelected(), source.isSelected());
+ assertSame(button.isCheckable(), source.isCheckable());
+ assertSame(button.isChecked(), source.isChecked());
+ }
+
+ @LargeTest
+ public void testObjectContract() throws Exception {
+ // find a view and make sure it is not focused
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ AccessibilityNodeInfo parent = button.getParent();
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ AccessibilityNodeInfo child = parent.getChild(i);
+ assertNotNull(child);
+ if (child.equals(button)) {
+ assertEquals("Equal objects must have same hasCode.", button.hashCode(),
+ child.hashCode());
+ return;
+ }
+ }
+ fail("Parent's children do not have the info whose parent is the parent.");
+ }
+
+ @Override
+ protected void scrubClass(Class<?> testCaseClass) {
+ /* intentionally do not scrub */
+ }
+
+ /**
+ * Starts the activity under tests and waits for the first accessibility
+ * event from that activity.
+ */
+ private void startActivityAndWaitForFirstEvent() {
+ AccessibilityQueryBridge bridge = AccessibilityQueryBridge.getInstance(
+ getInstrumentation().getContext());
+ bridge.perfromActionAndWaitForEvent(
+ new Runnable() {
+ @Override
+ public void run() {
+ getActivity();
+ }
+ },
+ new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ final int eventType = event.getEventType();
+ CharSequence packageName = event.getPackageName();
+ Context targetContext = getInstrumentation().getTargetContext();
+ return (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ && targetContext.getPackageName().equals(packageName));
+ }
+ });
+ }
+
+ /**
+ * @return The source of the last accessibility event.
+ */
+ private AccessibilityNodeInfo getAwaitedAccessibilityEventSource() {
+ AccessibilityQueryBridge bridge = AccessibilityQueryBridge.getInstance(
+ getInstrumentation().getContext());
+ AccessibilityEvent event = bridge.getAwaitedAccessibilityEvent();
+ if (event != null) {
+ return event.getSource();
+ }
+ return null;
+ }
+
+ private List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(AccessibilityNodeInfo root,
+ String text) {
+ if (root != null) {
+ return root.findAccessibilityNodeInfosByText(text);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Finds the first accessibility info that contains text. The search starts
+ * from the given <code>root</code>
+ *
+ * @param root Node from which to start the search.
+ * @param resId Resource id of the searched text.
+ * @return The node with this text or null.
+ */
+ private AccessibilityNodeInfo findAccessibilityNodeInfoByText(AccessibilityNodeInfo root,
+ int resId) {
+ return findAccessibilityNodeInfoByText(root,
+ getInstrumentation().getContext().getString(resId));
+ }
+
+ /**
+ * Finds the first accessibility info that contains text. The search starts
+ * from the given <code>root</code>
+ *
+ * @param root Node from which to start the search.
+ * @param text The searched text.
+ * @return The node with this text or null.
+ */
+ private AccessibilityNodeInfo findAccessibilityNodeInfoByText(AccessibilityNodeInfo root,
+ String text) {
+ List<AccessibilityNodeInfo> nodes = findAccessibilityNodeInfosByText(root, text);
+ if (nodes != null && !nodes.isEmpty()) {
+ return nodes.get(0);
+ }
+ return null;
+ }
+
+ /**
+ * This class serves as a bridge for querying the screen content.
+ * The bride is connected of a delegating accessibility service.
+ */
+ static class AccessibilityQueryBridge extends AccessibilityService {
+
+ /**
+ * The singleton instance.
+ */
+ private static AccessibilityQueryBridge sInstance;
+
+ /**
+ * Helper for connecting to the delegating accessibility service.
+ */
+ private final AccessibilityDelegateHelper mAccessibilityDelegateHelper;
+
+ /**
+ * The last received accessibility event.
+ */
+ private AccessibilityEvent mAwaitedAccessbiliyEvent;
+
+ /**
+ * Barrier for synchronizing waiting client and this bridge.
+ */
+ private CyclicBarrier mBarrier = new CyclicBarrier(2);
+
+ /**
+ * Filter for the currently waited event.
+ */
+ private AccessibilityEventFilter mWaitedFilter;
+
+ /**
+ * Gets the {@link AccessibilityQueryBridge} singleton.
+ *
+ * @param context A context handle.
+ * @return The mock service.
+ */
+ public static AccessibilityQueryBridge getInstance(Context context) {
+ if (sInstance == null) {
+ // since we do bind once and do not unbind from the delegating
+ // service and JUnit3 does not support @BeforeTest and @AfterTest,
+ // we will leak a service connection after the test but this
+ // does not affect the test results and the test is twice as fast
+ sInstance = new AccessibilityQueryBridge(context);
+ }
+ return sInstance;
+ }
+
+ private AccessibilityQueryBridge(Context context) {
+ mAccessibilityDelegateHelper = new AccessibilityDelegateHelper(this);
+ mAccessibilityDelegateHelper.bindToDelegatingAccessibilityService(context);
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ if (mWaitedFilter != null && mWaitedFilter.accept(event)) {
+ mAwaitedAccessbiliyEvent = AccessibilityEvent.obtain(event);
+ awaitOnBarrier();
+ }
+ }
+
+ @Override
+ public void onInterrupt() {
+ /* do nothing */
+ }
+
+ /**
+ * @return The event that was waited for.
+ */
+ public AccessibilityEvent getAwaitedAccessibilityEvent() {
+ return mAwaitedAccessbiliyEvent;
+ }
+
+ /**
+ * Performs an action and waits for the resulting event.
+ *
+ * @param action The action to perform.
+ * @param filter Filter for recognizing the waited event.
+ */
+ public void perfromActionAndWaitForEvent(Runnable action,
+ AccessibilityEventFilter filter) {
+ reset();
+ mWaitedFilter = filter;
+ action.run();
+ awaitOnBarrier();
+ }
+
+ /**
+ * Rests the internal state.
+ */
+ private void reset() {
+ if (mAwaitedAccessbiliyEvent != null) {
+ mAwaitedAccessbiliyEvent.recycle();
+ mAwaitedAccessbiliyEvent = null;
+ }
+ mBarrier.reset();
+ mWaitedFilter = null;
+ }
+
+ /**
+ * Calls await of the barrier taking care of the exceptions.
+ */
+ private void awaitOnBarrier() {
+ try {
+ mBarrier.await(AccessibilityDelegateHelper.TIMEOUT_ASYNC_PROCESSING,
+ TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ } catch (BrokenBarrierException bbe) {
+ /* ignore */
+ } catch (TimeoutException te) {
+ /* ignore */
+ }
+ }
+ }
+}