Text traversal at various granularities.

1. Implementing text content navigation at various granularities.
   For views that have content description but no text the
   content description is the traversed at character and word
   granularities. For views that inherit from TextView the
   supported granularities are character, word, line, and page.

bug:5932640

Change-Id: I6640c24b0f588ce35b5cd901cfc513faa1849eb9
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegateConnection.aidl b/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegateConnection.aidl
index 50da0d3..253a082 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegateConnection.aidl
+++ b/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegateConnection.aidl
@@ -15,6 +15,7 @@
  */
 package android.accessibilityservice;
 
+import android.os.Bundle;
 import android.os.IBinder;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -40,7 +41,8 @@
 
     AccessibilityNodeInfo focusSearch(in AccessibilityNodeInfo current, int direction);
 
-    boolean performAccessibilityAction(in AccessibilityNodeInfo target, int action);
+    boolean performAccessibilityAction(in AccessibilityNodeInfo target, int action,
+           in Bundle arguments);
 
     AccessibilityNodeInfo getSource(in AccessibilityEvent event);
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
index e5dfca7..b93e626 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
@@ -22,6 +22,7 @@
 import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
 import android.app.Service;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -169,8 +170,9 @@
             }
 
             @Override
-            public boolean performAccessibilityAction(AccessibilityNodeInfo target, int action) {
-                return target.performAction(action);
+            public boolean performAccessibilityAction(AccessibilityNodeInfo target, int action,
+                    Bundle arguments) {
+                return target.performAction(action, arguments);
             }
 
             @Override
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index 9dd05be..24cdea3 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -242,8 +242,8 @@
                 receivedEvent.getToIndex());
         assertSame("scrollable has incorect value", expectedEvent.isScrollable(),
                 receivedEvent.isScrollable());
-        assertSame("granularity has incorect value", expectedEvent.getGranularity(),
-                receivedEvent.getGranularity());
+        assertSame("granularity has incorect value", expectedEvent.getMovementGranularity(),
+                receivedEvent.getMovementGranularity());
 
         assertSame("parcelableData has incorect value",
                 ((Message) expectedEvent.getParcelableData()).what,
diff --git a/tests/tests/accessibilityservice/AndroidManifest.xml b/tests/tests/accessibilityservice/AndroidManifest.xml
index 678775d..59fc8b0 100644
--- a/tests/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/tests/accessibilityservice/AndroidManifest.xml
@@ -40,6 +40,9 @@
       <activity android:label="@string/accessibility_focus_and_input_focus_sync_test_activity"
               android:name="android.accessibilityservice.cts.AccessibilityFocusAndInputFocusSyncActivity"/>
 
+      <activity android:label="@string/accessibility_text_traversal_test_activity"
+              android:name="android.accessibilityservice.cts.AccessibilityTextTraversalActivity"/> 
+
   </application>
 
   <instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/tests/tests/accessibilityservice/res/layout/accessibility_text_traversal_test.xml b/tests/tests/accessibilityservice/res/layout/accessibility_text_traversal_test.xml
new file mode 100644
index 0000000..1c15f8e
--- /dev/null
+++ b/tests/tests/accessibilityservice/res/layout/accessibility_text_traversal_test.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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"
+    >
+
+   <View
+       android:id="@+id/view"
+       android:layout_width="50dip"
+       android:layout_height="50dip"
+       />
+
+   <TextView
+       android:id="@+id/text"
+       android:layout_width="200dip"
+       android:layout_height="200dip"
+       />
+
+   <EditText
+       android:id="@+id/edit"
+       android:layout_width="200dip"
+       android:layout_height="200dip"
+       android:maxLines="1000"
+       android:scrollbars="vertical"
+       />
+
+</LinearLayout>
diff --git a/tests/tests/accessibilityservice/res/values/strings.xml b/tests/tests/accessibilityservice/res/values/strings.xml
index 48a5052..16ad575 100644
--- a/tests/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/tests/accessibilityservice/res/values/strings.xml
@@ -125,4 +125,33 @@
     <!-- String title of the accessibility focus and input focus sync test activity -->
     <string name="accessibility_focus_and_input_focus_sync_test_activity">Accessibility focus and input focus sync test</string>
 
+    <!-- String title of the accessibility text traversal test activity. -->
+    <string name="accessibility_text_traversal_test_activity">Accessibility text traversal test</string>
+
+    <string name="android_wiki_short">
+        Android is a Linux-based operating system for mobile devices</string>
+
+    <string name="android_wiki">
+        Android is a Linux-based operating system for mobile devices such as smartphones and tablet
+        computers. It is developed by the Open Handset Alliance, led by Google, and other companies.
+        Google purchased the initial developer of the software, Android Inc., in 2005. The
+        unveiling of the Android distribution in 2007 was announced with the founding of the Open
+        Handset Alliance, a consortium of 86 hardware, software, and telecommunication companies
+        devoted to advancing open </string>
+
+    <string name="a_b">A B</string>
+
+    <string name="android_wiki_search">Android is a Linux-based</string>
+
+    <string name="foo_bar_baz">Foo bar baz.</string>
+
+    <string name="android_wiki_paragraphs">
+        \n\nAndroid is a Linux-based operating system for mobile devices such as smartphones and tablet
+        computers.\n\n It is developed by the Open Handset Alliance, led by Google, and other companies.
+        Google purchased the initial developer of the software, Android Inc., in 2005.\n The
+        unveiling of the Android distribution in 2007 was announced with the founding of the Open
+        Handset Alliance, a consortium of 86 hardware, software, and telecommunication companies
+        devoted to advancing open standards for mobile devices. Google releases the
+        Android code as open-source, under the Apache License.\n\n</string>
+
 </resources>
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegateConnection.aidl b/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegateConnection.aidl
index 50da0d3..253a082 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegateConnection.aidl
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegateConnection.aidl
@@ -15,6 +15,7 @@
  */
 package android.accessibilityservice;
 
+import android.os.Bundle;
 import android.os.IBinder;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -40,7 +41,8 @@
 
     AccessibilityNodeInfo focusSearch(in AccessibilityNodeInfo current, int direction);
 
-    boolean performAccessibilityAction(in AccessibilityNodeInfo target, int action);
+    boolean performAccessibilityAction(in AccessibilityNodeInfo target, int action,
+           in Bundle arguments);
 
     AccessibilityNodeInfo getSource(in AccessibilityEvent event);
 
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityActivityTestCase.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityActivityTestCase.java
index 80a41a2..e6ef33d 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityActivityTestCase.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityActivityTestCase.java
@@ -28,6 +28,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -360,11 +361,16 @@
         }
 
         public boolean performAction(AccessibilityNodeInfo target, int action) {
+            return performAction(target, action, null);
+        }
+
+        public boolean performAction(AccessibilityNodeInfo target, int action, Bundle arguments) {
             try {
                 // Sending a node info across processes recycles
                 // it so use a clone to avoid losing state
                 AccessibilityNodeInfo targetClone = AccessibilityNodeInfo.obtain(target);
-                return getQueryConnection().performAccessibilityAction(targetClone, action);
+                return getQueryConnection().performAccessibilityAction(targetClone, action,
+                        arguments);
             } catch (RemoteException re) {
                 /* ignore */
             }
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalActivity.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalActivity.java
new file mode 100644
index 0000000..7c9b45a
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalActivity.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2012 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 com.android.cts.accessibilityservice.R;
+
+/**
+ * Activity for testing the accessibility APIs for traversing the
+ * text content of a View at several granularities.
+ */
+public class AccessibilityTextTraversalActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.accessibility_text_traversal_test);
+    }
+}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
new file mode 100644
index 0000000..0bc59f8
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -0,0 +1,1726 @@
+/**
+ * Copyright (C) 2012 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.os.Bundle;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.Selection;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.cts.accessibilityservice.R;
+
+/**
+ * Test cases for testing the accessibility APIs for traversing the text content of
+ * a View at several granularities.
+ * <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 delegating service is installed and enabled during test execution. It serves
+ * as a proxy to the system used by the tests. This indirection is needed since the
+ * test runner stops the package before running the tests. Hence, if the accessibility
+ * service is in the test package running the tests would break the binding between
+ * the service and the system.  The delegating service is in
+ * <strong>CtsDelegatingAccessibilityService.apk</strong> whose source is located at
+ * <strong>cts/tests/accessibilityservice</strong>.
+ * </p>
+ */
+public class AccessibilityTextTraversalTest
+        extends AccessibilityActivityTestCase<AccessibilityTextTraversalActivity>{
+
+    public AccessibilityTextTraversalTest() {
+        super(AccessibilityTextTraversalActivity.class);
+    }
+
+    @MediumTest
+    public void testActionNextAndPreviousAtGranularityCharacterOverContentDescription()
+            throws Exception {
+
+        final View view = getActivity().findViewById(R.id.view);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                view.setContentDescription(getString(R.string.a_b));
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.a_b));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.a_b))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 1
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.a_b))
+                        && event.getFromIndex() == 1
+                        && event.getToIndex() == 2
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.a_b))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.a_b))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.a_b))
+                        && event.getFromIndex() == 1
+                        && event.getToIndex() == 2
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.a_b))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 1
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+    }
+
+    @MediumTest
+    public void testActionNextAndPreviousAtGranularityWordOverContentDescription()
+            throws Exception {
+        final View view = getActivity().findViewById(R.id.view);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                view.setContentDescription(getString(R.string.foo_bar_baz));
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 4
+                        && event.getToIndex() == 7
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 8
+                        && event.getToIndex() == 11
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 8
+                        && event.getToIndex() == 11
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 4
+                        && event.getToIndex() == 7
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+    }
+
+    @MediumTest
+    public void testActionNextAndPreviousAtGranularityCharacterOverText()
+            throws Exception {
+        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                textView.setText(getString(R.string.a_b));
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.a_b));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 1
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(textView.getText()));
+        assertEquals(0, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 1
+                        && event.getToIndex() == 2
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(1, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(textView.getText()));
+        assertEquals(2, Selection.getSelectionEnd(textView.getText()));
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Verify the selection position.
+        assertEquals(3, Selection.getSelectionStart(textView.getText()));
+        assertEquals(3, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 1
+                        && event.getToIndex() == 2
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(textView.getText()));
+        assertEquals(2, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 1
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(1, Selection.getSelectionEnd(textView.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(textView.getText()));
+    }
+
+    @MediumTest
+    public void testActionNextAndPreviousAtGranularityWordOverText() throws Exception {
+        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                textView.setText(getString(R.string.foo_bar_baz));
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(textView.getText()));
+        assertEquals(0, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 4
+                        && event.getToIndex() == 7
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(4, Selection.getSelectionStart(textView.getText()));
+        assertEquals(4, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 8
+                        && event.getToIndex() == 11
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(8, Selection.getSelectionStart(textView.getText()));
+        assertEquals(8, Selection.getSelectionEnd(textView.getText()));
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 8
+                        && event.getToIndex() == 11
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Verify the selection position.
+        assertEquals(11, Selection.getSelectionStart(textView.getText()));
+        assertEquals(11, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 4
+                        && event.getToIndex() == 7
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(7, Selection.getSelectionStart(textView.getText()));
+        assertEquals(7, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(3, Selection.getSelectionStart(textView.getText()));
+        assertEquals(3, Selection.getSelectionEnd(textView.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(textView.getText()));
+    }
+
+    @MediumTest
+    public void testActionNextAndPreviousAtGranularityLineOverText() throws Exception {
+        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                textView.setText(getString(R.string.android_wiki_short));
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.android_wiki_short));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 25
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(textView.getText()));
+        assertEquals(0, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 25
+                        && event.getToIndex() == 53
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(25, Selection.getSelectionStart(textView.getText()));
+        assertEquals(25, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 53
+                        && event.getToIndex() == 60
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(53, Selection.getSelectionStart(textView.getText()));
+        assertEquals(53, Selection.getSelectionEnd(textView.getText()));
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 53
+                        && event.getToIndex() == 60
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Verify the selection position.
+        assertEquals(60, Selection.getSelectionStart(textView.getText()));
+        assertEquals(60, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 25
+                        && event.getToIndex() == 53
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(53, Selection.getSelectionStart(textView.getText()));
+        assertEquals(53, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 25
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(25, Selection.getSelectionStart(textView.getText()));
+        assertEquals(25, Selection.getSelectionEnd(textView.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(textView.getText()));
+    }
+
+    @MediumTest
+    public void testActionNextAndPreviousAtGranularityPageOverText() throws Exception {
+        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setText(getString(R.string.android_wiki));
+                Selection.removeSelection(editText.getText());
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.android_wiki));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 156
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(0, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 156
+                        && event.getToIndex() == 318
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(156, Selection.getSelectionStart(editText.getText()));
+        assertEquals(156, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 318
+                        && event.getToIndex() == 472
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(318, Selection.getSelectionStart(editText.getText()));
+        assertEquals(318, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(editText.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 318
+                        && event.getToIndex() == 472
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Verify the selection position.
+        assertEquals(472, Selection.getSelectionStart(editText.getText()));
+        assertEquals(472, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 156
+                        && event.getToIndex() == 318
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(318, Selection.getSelectionStart(editText.getText()));
+        assertEquals(318, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 156
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(156, Selection.getSelectionStart(editText.getText()));
+        assertEquals(156, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(editText.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(editText.getText()));
+    }
+
+    @MediumTest
+    public void testActionNextAndPreviousAtGranularityParagraphOverText() throws Exception {
+        final TextView textView = (TextView) getActivity().findViewById(R.id.edit);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                textView.setText(getString(R.string.android_wiki_paragraphs));
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.android_wiki_short));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 106
+                        && event.getMovementGranularity()
+                            == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(textView.getText()));
+        assertEquals(2, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 106
+                        && event.getToIndex() == 268
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(106, Selection.getSelectionStart(textView.getText()));
+        assertEquals(106, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 268
+                        && event.getToIndex() == 584
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(268, Selection.getSelectionStart(textView.getText()));
+        assertEquals(268, Selection.getSelectionEnd(textView.getText()));
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 268
+                        && event.getToIndex() == 584
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Verify the selection position.
+        assertEquals(584, Selection.getSelectionStart(textView.getText()));
+        assertEquals(584, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 106
+                        && event.getToIndex() == 268
+                        && event.getMovementGranularity() ==
+                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(268, Selection.getSelectionStart(textView.getText()));
+        assertEquals(268, Selection.getSelectionEnd(textView.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 106
+                        && event.getMovementGranularity()
+                                == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(106, Selection.getSelectionStart(textView.getText()));
+        assertEquals(106, Selection.getSelectionEnd(textView.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(-1, Selection.getSelectionStart(textView.getText()));
+        assertEquals(-1, Selection.getSelectionEnd(textView.getText()));
+    }
+}