OSDN Git Service

original
[gb-231r1-is01/GB_2.3_IS01.git] / cts / tests / tests / accessibilityservice / src / android / accessibilityservice / cts / AccessibilityEndToEndTest.java
diff --git a/cts/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/cts/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
new file mode 100644 (file)
index 0000000..5259753
--- /dev/null
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2010 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.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.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;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * This class performs end-to-end testing of the accessibility feature by
+ * 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 delegating 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>CtsDelegatingAccessibilityService.apk</strong>
+ * whose source is located at <strong>cts/tests/accessibilityservice</strong> is required.
+ * Once the former package has been installed accessibility must be enabled (Settings ->
+ * Accessibility), the delegating service must be enabled (Settings -> Accessibility
+ * -> Delegating Accessibility Service), and then the CTS tests in this package can be
+ * successfully run. Further, the delegate and tests run in separate processes since
+ * the instrumentation restarts the process in which it is running and this
+ * breaks the binding between the delegating accessibility service and the system.
+ */
+public class AccessibilityEndToEndTest extends
+        ActivityInstrumentationTestCase2<AccessibilityEndToEndTestActivity> {
+
+    /**
+     * Timeout required for pending Binder calls or event processing to
+     * complete.
+     */
+    private static final long MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING = 1000;
+
+    /**
+     * 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}.
+     *
+     * @throws Exception If any error occurs.
+     */
+    public AccessibilityEndToEndTest() throws Exception {
+        super("com.android.cts.accessibilityservice", AccessibilityEndToEndTestActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // wait for the activity to settle down so we do not receive
+        // the event for its start, thus breaking the tests
+        getInstrumentation().waitForIdleSync();
+    }
+
+    @LargeTest
+    public void testTypeViewSelectedAccessibilityEvent() throws Throwable {
+        Activity activity = getActivity();
+
+        // create and populate the expected event
+        AccessibilityEvent selectedEvent = AccessibilityEvent.obtain();
+        selectedEvent.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
+        selectedEvent.setClassName(ListView.class.getName());
+        selectedEvent.setPackageName(getActivity().getPackageName());
+        selectedEvent.getText().add(activity.getString(R.string.second_list_item));
+        selectedEvent.setItemCount(2);
+        selectedEvent.setCurrentItemIndex(1);
+        selectedEvent.setEnabled(true);
+
+        // set expectations
+        MockAccessibilityService service = MockAccessibilityService.getInstance(activity);
+        service.expectEvent(selectedEvent);
+        service.replay();
+
+        // trigger the event
+        final ListView listView = (ListView) activity.findViewById(R.id.listview);
+        getActivity().runOnUiThread(new Runnable() {
+            public void run() {
+                listView.setSelection(1);
+            }
+        });
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testTypeViewClickedAccessibilityEvent() throws Throwable {
+        Activity activity = getActivity();
+
+        // create and populate the expected event
+        AccessibilityEvent clickedEvent = AccessibilityEvent.obtain();
+        clickedEvent.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
+        clickedEvent.setClassName(Button.class.getName());
+        clickedEvent.setPackageName(getActivity().getPackageName());
+        clickedEvent.getText().add(activity.getString(R.string.button_title));
+        clickedEvent.setEnabled(true);
+
+        // set expectations
+        MockAccessibilityService service = MockAccessibilityService.getInstance(activity);
+        service.expectEvent(clickedEvent);
+        service.replay();
+
+        // trigger the event
+        final Button button = (Button) activity.findViewById(R.id.button);
+        activity.runOnUiThread(new Runnable() {
+            public void run() {
+                button.performClick();
+            }
+        });
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testTypeViewLongClickedAccessibilityEvent() throws Throwable {
+        Activity activity = getActivity();
+
+        // create and populate the expected event
+        AccessibilityEvent longClickedEvent = AccessibilityEvent.obtain();
+        longClickedEvent.setEventType(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+        longClickedEvent.setClassName(Button.class.getName());
+        longClickedEvent.setPackageName(getActivity().getPackageName());
+        longClickedEvent.getText().add(activity.getString(R.string.button_title));
+        longClickedEvent.setEnabled(true);
+
+        // set expectations
+        MockAccessibilityService service = MockAccessibilityService.getInstance(activity);
+        service.expectEvent(longClickedEvent);
+        service.replay();
+
+        // trigger the event
+        final Button button = (Button) activity.findViewById(R.id.button);
+        activity.runOnUiThread(new Runnable() {
+            public void run() {
+                button.performLongClick();
+            }
+        });
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testTypeViewFocusedAccessibilityEvent() throws Throwable {
+        Activity activity = getActivity();
+
+        // create and populate the expected event
+        AccessibilityEvent focusedEvent = AccessibilityEvent.obtain();
+        focusedEvent.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+        focusedEvent.setClassName(Button.class.getName());
+        focusedEvent.setPackageName(getActivity().getPackageName());
+        focusedEvent.getText().add(activity.getString(R.string.button_title));
+        focusedEvent.setItemCount(3);
+        focusedEvent.setCurrentItemIndex(2);
+        focusedEvent.setEnabled(true);
+
+        // set expectations
+        MockAccessibilityService service = MockAccessibilityService.getInstance(activity);
+        service.expectEvent(focusedEvent);
+        service.replay();
+
+        // trigger the event
+        final Button button = (Button) activity.findViewById(R.id.button);
+        activity.runOnUiThread(new Runnable() {
+            public void run() {
+                button.requestFocus();
+            }
+        });
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testTypeViewTextChangedAccessibilityEvent() throws Throwable {
+        Activity activity = getActivity();
+
+        // focus the edit text
+        final EditText editText = (EditText) activity.findViewById(R.id.edittext);
+        activity.runOnUiThread(new Runnable() {
+            public void run() {
+                editText.requestFocus();
+            }
+        });
+
+        // wait for the generated focus event to be dispatched
+        Thread.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+
+        final String beforeText = activity.getString(R.string.text_input_blah);
+        final String newText = activity.getString(R.string.text_input_blah_blah);
+        final String afterText = beforeText.substring(0, 3) + newText;
+
+        // create and populate the expected event
+        AccessibilityEvent textChangedEvent = AccessibilityEvent.obtain();
+        textChangedEvent.setEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+        textChangedEvent.setClassName(EditText.class.getName());
+        textChangedEvent.setPackageName(getActivity().getPackageName());
+        textChangedEvent.getText().add(afterText);
+        textChangedEvent.setBeforeText(beforeText);
+        textChangedEvent.setFromIndex(3);
+        textChangedEvent.setAddedCount(9);
+        textChangedEvent.setRemovedCount(1);
+        textChangedEvent.setEnabled(true);
+
+        // set expectations
+        MockAccessibilityService service = MockAccessibilityService.getInstance(activity);
+        service.expectEvent(textChangedEvent);
+        service.replay();
+
+        // trigger the event
+        activity.runOnUiThread(new Runnable() {
+            public void run() {
+                editText.getEditableText().replace(3, 4, newText);
+            }
+        });
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testTypeWindowStateChangedAccessibilityEvent() throws Throwable {
+        Activity activity = getActivity();
+        String title = activity.getString(R.string.alert_title);
+        String message = activity.getString(R.string.alert_message);
+
+        // create and populate the expected event
+        AccessibilityEvent windowStateChangedEvent = AccessibilityEvent.obtain();
+        windowStateChangedEvent.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        windowStateChangedEvent.setClassName(AlertDialog.class.getName());
+        windowStateChangedEvent.setPackageName(getActivity().getPackageName());
+        windowStateChangedEvent.getText().add(title);
+        windowStateChangedEvent.getText().add(message);
+        windowStateChangedEvent.setEnabled(true);
+
+        // set expectations
+        MockAccessibilityService service = MockAccessibilityService.getInstance(activity);
+        service.expectEvent(windowStateChangedEvent);
+        service.replay();
+
+        // trigger the event
+        final EditText editText = (EditText) activity.findViewById(R.id.edittext);
+        activity.runOnUiThread(new Runnable() {
+            public void run() {
+                AlertDialog dialog = (new AlertDialog.Builder(getActivity())
+                        .setTitle(R.string.alert_title).setMessage(R.string.alert_message))
+                        .create();
+                dialog.show();
+            }
+        });
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testTypeNotificationStateChangedAccessibilityEvent() throws Throwable {
+        Activity activity = getActivity();
+        String message = activity.getString(R.string.notification_message);
+
+        // create the notification to send
+        int notificationId = 1;
+        Notification notification = new Notification();
+        notification.icon = android.R.drawable.stat_notify_call_mute;
+        notification.contentIntent = PendingIntent.getActivity(getActivity(), 0, new Intent(),
+                PendingIntent.FLAG_CANCEL_CURRENT);
+        notification.tickerText = message;
+        notification.setLatestEventInfo(getActivity(), "", "", notification.contentIntent);
+
+        // create and populate the expected event
+        AccessibilityEvent notificationChangedEvent = AccessibilityEvent.obtain();
+        notificationChangedEvent.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+        notificationChangedEvent.setClassName(Notification.class.getName());
+        notificationChangedEvent.setPackageName(getActivity().getPackageName());
+        notificationChangedEvent.getText().add(message);
+        notificationChangedEvent.setParcelableData(notification);
+
+        // set expectations
+        MockAccessibilityService service = MockAccessibilityService.getInstance(activity);
+        service.expectEvent(notificationChangedEvent);
+        service.replay();
+
+        // trigger the event
+        NotificationManager notificationManager = (NotificationManager) activity
+                .getSystemService(Service.NOTIFICATION_SERVICE);
+        notificationManager.notify(notificationId, notification);
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+
+        // 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;
+
+        // 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 {
+
+        /**
+         * The singleton instance.
+         */
+        private static MockAccessibilityService sInstance;
+
+        /**
+         * The events this service expects to receive.
+         */
+        private final Queue<AccessibilityEvent> mExpectedEvents =
+            new LinkedList<AccessibilityEvent>();
+
+        /**
+         * Interruption call this service expects to receive.
+         */
+        private boolean mExpectedInterrupt;
+
+        /**
+         * Flag if the mock is currently replaying.
+         */
+        private boolean mReplaying;
+
+        /**
+         * Flag indicating if this mock is initialized.
+         */
+        private boolean mInitialized;
+
+        /**
+         * The {@link Context} whose services to utilize.
+         */
+        private Context mContext;
+
+        /**
+         * Gets the {@link MockAccessibilityService} singleton.
+         *
+         * @param context A context handle.
+         * @return The mock service.
+         */
+        public static MockAccessibilityService 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 MockAccessibilityService(context);
+            }
+            return sInstance;
+        }
+
+        /**
+         * 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 */
+                }
+            }
+        }
+
+        /**
+         * Starts replaying the mock.
+         */
+        private void replay() {
+            mReplaying = true;
+        }
+
+        /**
+         * Verifies if all expected service methods have been called.
+         */
+        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");
+                }
+            }
+        }
+
+        /**
+         * Resets this instance so it can be reused.
+         */
+        private void reset() {
+            synchronized (this) {
+                mExpectedEvents.clear();
+                mExpectedInterrupt = false;
+                mReplaying = false;
+            }
+        }
+
+        /**
+         * Sets an expected call to
+         * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as
+         * argument.
+         *
+         * @param expectedEvent The expected event argument.
+         */
+        private void expectEvent(AccessibilityEvent expectedEvent) {
+            mExpectedEvents.add(expectedEvent);
+        }
+
+        /**
+         * Sets an expected call of {@link #onInterrupt()}.
+         */
+        public void expectInterrupt() {
+            mExpectedInterrupt = true;
+        }
+
+        @Override
+        public void onAccessibilityEvent(AccessibilityEvent receivedEvent) {
+            synchronized (this) {
+                if (!mReplaying) {
+                    return;
+                }
+                if (mExpectedEvents.isEmpty()) {
+                    throw new IllegalStateException("Unexpected event: " + receivedEvent);
+                }
+                AccessibilityEvent expectedEvent = mExpectedEvents.poll();
+                assertEqualsAccessiblityEvent(expectedEvent, receivedEvent);
+
+            }
+        }
+
+        @Override
+        public void onInterrupt() {
+            synchronized (this) {
+                if (!mReplaying) {
+                    return;
+                }
+
+                if (!mExpectedInterrupt) {
+                    throw new IllegalStateException("Unexpected call to onInterrupt()");
+                }
+
+                mExpectedInterrupt = false;
+            }
+        }
+
+        /**
+         * {@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.
+         */
+        private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent,
+                AccessibilityEvent receivedEvent) {
+            TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(),
+                    receivedEvent.getAddedCount());
+            TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(),
+                    receivedEvent.getBeforeText());
+            TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(),
+                    receivedEvent.isChecked());
+            TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(),
+                    receivedEvent.getClassName());
+            TestCase.assertEquals("contentDescription has incorrect value", expectedEvent
+                    .getContentDescription(), receivedEvent.getContentDescription());
+            TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent
+                    .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex());
+            TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(),
+                    receivedEvent.isEnabled());
+            TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(),
+                    receivedEvent.getEventType());
+            TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(),
+                    receivedEvent.getFromIndex());
+            TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(),
+                    receivedEvent.isFullScreen());
+            TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(),
+                    receivedEvent.getItemCount());
+            // This will fail due to a bug fixed in Gingerbread. Bug 2593810 (removed the method).
+            // assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent);
+            TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(),
+                    receivedEvent.isPassword());
+            TestCase.assertEquals("removedCount has incorrect value", expectedEvent
+                    .getRemovedCount(), receivedEvent.getRemovedCount());
+            assertEqualsText(expectedEvent, receivedEvent);
+        }
+
+        /**
+         * Compares the text of the <code>expectedEvent</code> and
+         * <code>receivedEvent</code> by comparing the string representation of
+         * the corresponding {@link CharSequence}s.
+         */
+        private void assertEqualsText(AccessibilityEvent expectedEvent,
+                AccessibilityEvent receivedEvent) {
+            String message = "text has incorrect value";
+            List<CharSequence> expectedText = expectedEvent.getText();
+            List<CharSequence> receivedText = receivedEvent.getText();
+
+            TestCase.assertEquals(message, expectedText.size(), receivedText.size());
+
+            Iterator<CharSequence> expectedTextIterator = expectedText.iterator();
+            Iterator<CharSequence> receivedTextIterator = receivedText.iterator();
+
+            for (int i = 0; i < expectedText.size(); i++) {
+                // compare the string representation
+                TestCase.assertEquals(message, expectedTextIterator.next().toString(),
+                        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;
+                }
+            }
+        }
+    }
+}