Merge "Adding explicit text traversal granularities and actions for web navigation."
diff --git a/api/current.txt b/api/current.txt
index 408d044..f2b35a8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24930,6 +24930,7 @@
     method public static java.lang.String eventTypeToString(int);
     method public long getEventTime();
     method public int getEventType();
+    method public int getGranularity();
     method public java.lang.CharSequence getPackageName();
     method public android.view.accessibility.AccessibilityRecord getRecord(int);
     method public int getRecordCount();
@@ -24939,6 +24940,7 @@
     method public static android.view.accessibility.AccessibilityEvent obtain();
     method public void setEventTime(long);
     method public void setEventType(int);
+    method public void setGranularity(int);
     method public void setPackageName(java.lang.CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
@@ -24960,6 +24962,7 @@
     field public static final int TYPE_VIEW_SELECTED = 4; // 0x4
     field public static final int TYPE_VIEW_TEXT_CHANGED = 16; // 0x10
     field public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192; // 0x2000
+    field public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_GRANULARITY = 131072; // 0x20000
     field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
     field public static final int TYPE_WINDOW_STATE_CHANGED = 32; // 0x20
   }
@@ -25000,7 +25003,7 @@
     method public int getChildCount();
     method public java.lang.CharSequence getClassName();
     method public java.lang.CharSequence getContentDescription();
-    method public java.lang.CharSequence[] getGranularities();
+    method public int getGranularities();
     method public java.lang.CharSequence getPackageName();
     method public android.view.accessibility.AccessibilityNodeInfo getParent();
     method public java.lang.CharSequence getText();
@@ -25034,7 +25037,7 @@
     method public void setEnabled(boolean);
     method public void setFocusable(boolean);
     method public void setFocused(boolean);
-    method public void setGranularities(java.lang.CharSequence[]);
+    method public void setGranularities(int);
     method public void setLongClickable(boolean);
     method public void setPackageName(java.lang.CharSequence);
     method public void setParent(android.view.View);
@@ -25047,7 +25050,8 @@
     method public void setText(java.lang.CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40
-    field public static final java.lang.String ACTION_ARGUMENT_GRANULARITY = "ACTION_ARGUMENT_GRANULARITY";
+    field public static final java.lang.String ACTION_ARGUMENT_GRANULARITY_INT = "ACTION_ARGUMENT_GRANULARITY_INT";
+    field public static final java.lang.String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
     field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80
     field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2
     field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8
@@ -25055,11 +25059,18 @@
     field public static final int ACTION_FOCUS = 1; // 0x1
     field public static final int ACTION_LONG_CLICK = 32; // 0x20
     field public static final int ACTION_NEXT_AT_GRANULARITY = 256; // 0x100
+    field public static final int ACTION_NEXT_HTML_ELEMENT = 1024; // 0x400
     field public static final int ACTION_PREVIOUS_AT_GRANULARITY = 512; // 0x200
+    field public static final int ACTION_PREVIOUS_HTML_ELEMENT = 2048; // 0x800
     field public static final int ACTION_SELECT = 4; // 0x4
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
+    field public static final int GRANULARITY_CHARACTER = 1; // 0x1
+    field public static final int GRANULARITY_LINE = 4; // 0x4
+    field public static final int GRANULARITY_PAGE = 16; // 0x10
+    field public static final int GRANULARITY_PARAGRAPH = 8; // 0x8
+    field public static final int GRANULARITY_WORD = 2; // 0x2
   }
 
   public abstract class AccessibilityNodeProvider {
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 6cb1578..6d1166e 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -226,6 +226,23 @@
  *   <li>{@link #getContentDescription()} - The content description of the source.</li>
  * </ul>
  * </p>
+ * <b>View text traversed at granularity</b> - represents the event of traversing the
+ * text of a view at a given granularity. For example, moving to the next word.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_TEXT_TRAVERSED_AT_GRANULARITY} </br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the current text at the granularity.</li>
+ *   <li>{@link #isPassword()} - Whether the source is password.</li>
+ *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ *   <li>{@link #getContentDescription()} - The content description of the source.</li>
+ *   <li>{@link #getGranularity()} - Sets the granularity at which a view's text was traversed.</li>
+ * </ul>
+ * </p>
  * <p>
  * <b>View scrolled</b> - represents the event of scrolling a view. If
  * the source is a descendant of {@link android.widget.AdapterView} the
@@ -580,6 +597,11 @@
     public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000;
 
     /**
+     * Represents the event of traversing the text of a view at a given granularity.
+     */
+    public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_GRANULARITY = 0x00020000;
+
+    /**
      * Mask for {@link AccessibilityEvent} all types.
      *
      * @see #TYPE_VIEW_CLICKED
@@ -597,6 +619,7 @@
      * @see #TYPE_VIEW_SCROLLED
      * @see #TYPE_VIEW_TEXT_SELECTION_CHANGED
      * @see #TYPE_ANNOUNCEMENT
+     * @see #TYPE_VIEW_TEXT_TRAVERSED_AT_GRANULARITY
      */
     public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
 
@@ -610,6 +633,7 @@
     private int mEventType;
     private CharSequence mPackageName;
     private long mEventTime;
+    int mGranularity;
 
     private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>();
 
@@ -627,6 +651,7 @@
     void init(AccessibilityEvent event) {
         super.init(event);
         mEventType = event.mEventType;
+        mGranularity = event.mGranularity;
         mEventTime = event.mEventTime;
         mPackageName = event.mPackageName;
     }
@@ -744,6 +769,27 @@
     }
 
     /**
+     * Sets the text granularity that was traversed.
+     *
+     * @param granularity The granularity.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setGranularity(int granularity) {
+        enforceNotSealed();
+        mGranularity = granularity;
+    }
+
+    /**
+     * Gets the text granularity that was traversed.
+     *
+     * @return The granularity.
+     */
+    public int getGranularity() {
+        return mGranularity;
+    }
+
+    /**
      * Returns a cached instance if such is available or a new one is
      * instantiated with its type property set.
      *
@@ -831,6 +877,7 @@
     protected void clear() {
         super.clear();
         mEventType = 0;
+        mGranularity = 0;
         mPackageName = null;
         mEventTime = 0;
         while (!mRecords.isEmpty()) {
@@ -847,6 +894,7 @@
     public void initFromParcel(Parcel parcel) {
         mSealed = (parcel.readInt() == 1);
         mEventType = parcel.readInt();
+        mGranularity = parcel.readInt();
         mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         mEventTime = parcel.readLong();
         mConnectionId = parcel.readInt();
@@ -897,6 +945,7 @@
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(isSealed() ? 1 : 0);
         parcel.writeInt(mEventType);
+        parcel.writeInt(mGranularity);
         TextUtils.writeToParcel(mPackageName, parcel, 0);
         parcel.writeLong(mEventTime);
         parcel.writeInt(mConnectionId);
@@ -953,6 +1002,7 @@
         builder.append("EventType: ").append(eventTypeToString(mEventType));
         builder.append("; EventTime: ").append(mEventTime);
         builder.append("; PackageName: ").append(mPackageName);
+        builder.append("; Granularity: ").append(mGranularity);
         builder.append(super.toString());
         if (DEBUG) {
             builder.append("\n");
@@ -1033,6 +1083,8 @@
                 return "TYPE_VIEW_ACCESSIBILITY_FOCUSED";
             case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
                 return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED";
+            case TYPE_VIEW_TEXT_TRAVERSED_AT_GRANULARITY:
+                return "TYPE_CURRENT_AT_GRANULARITY_CHANGED";
             default:
                 return null;
         }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 3cb3b54..aec18fe 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -102,7 +102,7 @@
     public static final int ACTION_CLEAR_SELECTION = 0x00000008;
 
     /**
-     * Action that clicks on the node info.
+     * Action that long clicks on the node info.
      */
     public static final int ACTION_CLICK = 0x00000010;
 
@@ -122,78 +122,105 @@
     public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080;
 
     /**
-     * Action that requests from the node to go to the next entity in its content
-     * at a given granularity. For example, move to the next word, link, etc.
+     * Action that requests to go to the next entity in this node's text
+     * at a given granularity. For example, move to the next character, word, etc.
      * <p>
-     * <strong>Arguments:</strong>
-     * <ul>
-     * <li>
-     * {@link #ACTION_ARGUMENT_GRANULARITY}
-     * </li>
-     * <li>
-     * </p>
-     * <p>
+     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_GRANULARITY_INT}<br>
      * <strong>Example:</strong>
      * <code><pre><p>
-     *   // Assume the first granularity was presented to the user and she is
-     *   // making an explicit action to traverse the node at that granularity.
-     *   CharSequence granularity = info.getGranularity(0);
      *   Bundle arguments = new Bundle();
-     *   arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_GRANULARITY, granularity);
+     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_GRANULARITY_INT,
+     *           AccessibilityNodeInfo.GRANULARITY_CHARACTER);
      *   info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_GRANULARITY, arguments);
      * </code></pre></p>
-     * </li>
-     * </ul>
      * </p>
-     * @see #setGranularities(CharSequence[])
+     *
+     * @see #setGranularities(int)
      * @see #getGranularities()
+     *
+     * @see #GRANULARITY_CHARACTER
+     * @see #GRANULARITY_WORD
+     * @see #GRANULARITY_LINE
+     * @see #GRANULARITY_PARAGRAPH
+     * @see #GRANULARITY_PAGE
      */
     public static final int ACTION_NEXT_AT_GRANULARITY = 0x00000100;
 
     /**
-     * Action that requests from the node to go to the previous entity in its content
-     * at a given granularity. For example, move to the next word, link, etc.
+     * Action that requests to go to the previous entity in this node's text
+     * at a given granularity. For example, move to the next character, word, etc.
      * <p>
-     * <strong>Arguments:</strong>
-     * <ul>
-     * <li>
-     *  {@link #ACTION_ARGUMENT_GRANULARITY}
-     * </li>
-     * <li>
-     * </p>
-     * <p>
+     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_GRANULARITY_INT}<br>
      * <strong>Example:</strong>
      * <code><pre><p>
-     *   // Assume the first granularity was presented to the user and she is
-     *   // making an explicit action to traverse the node at that granularity.
-     *   CharSequence granularity = info.getGranularity(0);
      *   Bundle arguments = new Bundle();
-     *   arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_GRANULARITY, granularity);
-     *   info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_GRANULARITY, arguments);
+     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_GRANULARITY_INT,
+     *           AccessibilityNodeInfo.GRANULARITY_CHARACTER);
+     *   info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_GRANULARITY, arguments);
      * </code></pre></p>
-     * </li>
-     * </ul>
      * </p>
-     * @see #setGranularities(CharSequence[])
+     *
+     * @see #setGranularities(int)
      * @see #getGranularities()
+     *
+     * @see #GRANULARITY_CHARACTER
+     * @see #GRANULARITY_WORD
+     * @see #GRANULARITY_LINE
+     * @see #GRANULARITY_PARAGRAPH
+     * @see #GRANULARITY_PAGE
      */
     public static final int ACTION_PREVIOUS_AT_GRANULARITY = 0x00000200;
 
     /**
-     * Argument for which content granularity to be used when traversing the node content.
+     * Action to move to the next HTML element of a given type. For example, move
+     * to the BUTTON, INPUT, TABLE, etc.
      * <p>
-     * <strong>Actions:</strong>
-     * <ul>
-     * <li>
-     * {@link #ACTION_PREVIOUS_AT_GRANULARITY}
-     * </li>
-     * <li>
-     * {@link #ACTION_PREVIOUS_AT_GRANULARITY}
-     * </li>
-     * </ul>
+     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+     * <strong>Example:</strong>
+     * <code><pre><p>
+     *   Bundle arguments = new Bundle();
+     *   arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+     *   info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments);
+     * </code></pre></p>
      * </p>
      */
-    public static final String ACTION_ARGUMENT_GRANULARITY = "ACTION_ARGUMENT_GRANULARITY";
+    public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400;
+
+    /**
+     * Action to move to the previous HTML element of a given type. For example, move
+     * to the BUTTON, INPUT, TABLE, etc.
+     * <p>
+     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+     * <strong>Example:</strong>
+     * <code><pre><p>
+     *   Bundle arguments = new Bundle();
+     *   arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+     *   info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, arguments);
+     * </code></pre></p>
+     * </p>
+     */
+    public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800;
+
+    /**
+     * Argument for which text granularity to be used when traversing the node text.
+     * <p>
+     * <strong>Type:</strong> int<br>
+     * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_GRANULARITY},
+     * {@link #ACTION_PREVIOUS_AT_GRANULARITY}
+     * </p>
+     */
+    public static final String ACTION_ARGUMENT_GRANULARITY_INT = "ACTION_ARGUMENT_GRANULARITY_INT";
+
+    /**
+     * Argument for which HTML element to get moving to the next/previous HTML element.
+     * <p>
+     * <strong>Type:</strong> String<br>
+     * <strong>Actions:</strong> {@link #ACTION_NEXT_HTML_ELEMENT},
+     *         {@link #ACTION_PREVIOUS_HTML_ELEMENT}
+     * </p>
+     */
+    public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING =
+        "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
 
     /**
      * The input focus.
@@ -205,6 +232,33 @@
      */
     public static final int FOCUS_ACCESSIBILITY = 2;
 
+    // Granularities
+
+    /**
+     * Granularity bit for traversing the text of a node by character.
+     */
+    public static final int GRANULARITY_CHARACTER = 0x00000001;
+
+    /**
+     * Granularity bit for traversing the text of a node by word.
+     */
+    public static final int GRANULARITY_WORD = 0x00000002;
+
+    /**
+     * Granularity bit for traversing the text of a node by line.
+     */
+    public static final int GRANULARITY_LINE = 0x00000004;
+
+    /**
+     * Granularity bit for traversing the text of a node by paragraph.
+     */
+    public static final int GRANULARITY_PARAGRAPH = 0x00000008;
+
+    /**
+     * Granularity bit for traversing the text of a node by page.
+     */
+    public static final int GRANULARITY_PAGE = 0x00000010;
+
     // Boolean attributes.
 
     private static final int PROPERTY_CHECKABLE = 0x00000001;
@@ -308,7 +362,7 @@
     private final SparseLongArray mChildNodeIds = new SparseLongArray();
     private int mActions;
 
-    private CharSequence[] mGranularities;
+    private int mGranularities;
 
     private int mConnectionId = UNDEFINED;
 
@@ -532,28 +586,28 @@
     }
 
     /**
-     * Sets the granularities for traversing the content of this node.
+     * Sets the text granularities for traversing the text of this node.
      * <p>
      *   <strong>Note:</strong> Cannot be called from an
      *   {@link android.accessibilityservice.AccessibilityService}.
      *   This class is made immutable before being delivered to an AccessibilityService.
      * </p>
      *
-     * @param granularities The granularity names.
+     * @param granularities The bit mask with granularities.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
      */
-    public void setGranularities(CharSequence[] granularities) {
+    public void setGranularities(int granularities) {
         enforceNotSealed();
         mGranularities = granularities;
     }
 
     /**
-     * Gets the granularities for traversing the content of this node.
+     * Gets the granularities for traversing the text of this node.
      *
-     * @return The count.
+     * @return The bit mask with granularities.
      */
-    public CharSequence[] getGranularities() {
+    public int getGranularities() {
         return mGranularities;
     }
 
@@ -1339,8 +1393,6 @@
         parcel.writeLong(mParentNodeId);
         parcel.writeInt(mConnectionId);
 
-        parcel.writeCharSequenceArray(mGranularities);
-
         SparseLongArray childIds = mChildNodeIds;
         final int childIdsSize = childIds.size();
         parcel.writeInt(childIdsSize);
@@ -1360,6 +1412,8 @@
 
         parcel.writeInt(mActions);
 
+        parcel.writeInt(mGranularities);
+
         parcel.writeInt(mBooleanProperties);
 
         parcel.writeCharSequence(mPackageName);
@@ -1392,7 +1446,7 @@
         mContentDescription = other.mContentDescription;
         mActions= other.mActions;
         mBooleanProperties = other.mBooleanProperties;
-        mGranularities = (other.mGranularities) != null ? other.mGranularities.clone() : null;
+        mGranularities = other.mGranularities;
         final int otherChildIdCount = other.mChildNodeIds.size();
         for (int i = 0; i < otherChildIdCount; i++) {
             mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i));    
@@ -1411,8 +1465,6 @@
         mParentNodeId = parcel.readLong();
         mConnectionId = parcel.readInt();
 
-        mGranularities = parcel.readCharSequenceArray();
-
         SparseLongArray childIds = mChildNodeIds;
         final int childrenSize = parcel.readInt();
         for (int i = 0; i < childrenSize; i++) {
@@ -1432,6 +1484,8 @@
 
         mActions = parcel.readInt();
 
+        mGranularities = parcel.readInt();
+
         mBooleanProperties = parcel.readInt();
 
         mPackageName = parcel.readCharSequence();
@@ -1449,7 +1503,7 @@
         mParentNodeId = ROOT_NODE_ID;
         mWindowId = UNDEFINED;
         mConnectionId = UNDEFINED;
-        mGranularities = null;
+        mGranularities = 0;
         mChildNodeIds.clear();
         mBoundsInParent.set(0, 0, 0, 0);
         mBoundsInScreen.set(0, 0, 0, 0);
@@ -1482,6 +1536,29 @@
         }
     }
 
+    /**
+     * Gets the human readable granularity symbolic name.
+     *
+     * @param granularity The action.
+     * @return The symbolic name.
+     */
+    private static String getGranularitySymbolicName(int granularity) {
+        switch (granularity) {
+            case GRANULARITY_CHARACTER:
+                return "GRANULARITY_CHARACTER";
+            case GRANULARITY_WORD:
+                return "GRANULARITY_WORD";
+            case GRANULARITY_LINE:
+                return "GRANULARITY_LINE";
+            case GRANULARITY_PARAGRAPH:
+                return "GRANULARITY_PARAGRAPH";
+            case GRANULARITY_PAGE:
+                return "GRANULARITY_PAGE";
+            default:
+                throw new IllegalArgumentException("Unknown granularity: " + granularity);
+        }
+    }
+
     private boolean canPerformRequestOverConnection(long accessibilityNodeId) {
         return (mWindowId != UNDEFINED
                 && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED
@@ -1529,11 +1606,13 @@
             builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
             builder.append("; mParentNodeId: " + mParentNodeId);
 
-            CharSequence[] granularities = mGranularities;
+            int granularities = mGranularities;
             builder.append("; granularities: [");
-            for (int i = 0, count = granularities.length; i < count; i++) {
-                builder.append(granularities[i]);
-                if (i < count - 1) {
+            while (granularities != 0) {
+                final int granularity = 1 << Integer.numberOfTrailingZeros(granularities);
+                granularities &= ~granularity;
+                builder.append(getGranularitySymbolicName(granularity));
+                if (granularities != 0) {
                     builder.append(", ");
                 }
             }
@@ -1570,7 +1649,6 @@
         builder.append("; scrollable: " + isScrollable());
 
         builder.append("; [");
-
         for (int actionBits = mActions; actionBits != 0;) {
             final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
             actionBits &= ~action;
@@ -1579,7 +1657,6 @@
                 builder.append(", ");
             }
         }
-
         builder.append("]");
 
         return builder.toString();
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index b9404f3..01ddf1f 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1554,7 +1554,7 @@
             IAccessibilityInteractionConnection connection = null;
             synchronized (mLock) {
                 final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this,
-                        resolvedWindowId, action);
+                        resolvedWindowId, action, arguments);
                 if (!permissionGranted) {
                     return false;
                 } else {
@@ -1702,7 +1702,16 @@
             | AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
             | AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS
             | AccessibilityNodeInfo.ACTION_NEXT_AT_GRANULARITY
-            | AccessibilityNodeInfo.ACTION_PREVIOUS_AT_GRANULARITY;
+            | AccessibilityNodeInfo.ACTION_PREVIOUS_AT_GRANULARITY
+            | AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT
+            | AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT;
+
+        private static final int VALID_GRANULARITIES =
+            AccessibilityNodeInfo.GRANULARITY_CHARACTER
+            | AccessibilityNodeInfo.GRANULARITY_WORD
+            | AccessibilityNodeInfo.GRANULARITY_LINE
+            | AccessibilityNodeInfo.GRANULARITY_PARAGRAPH
+            | AccessibilityNodeInfo.GRANULARITY_PAGE;
 
         private static final int RETRIEVAL_ALLOWING_EVENT_TYPES =
             AccessibilityEvent.TYPE_VIEW_CLICKED
@@ -1752,10 +1761,12 @@
             return canRetrieveWindowContent(service) && isRetrievalAllowingWindow(windowId);
         }
 
-        public boolean canPerformActionLocked(Service service, int windowId, int action) {
+        public boolean canPerformActionLocked(Service service, int windowId, int action,
+                Bundle arguments) {
             return canRetrieveWindowContent(service)
                 && isRetrievalAllowingWindow(windowId)
-                && isActionPermitted(action);
+                && isActionPermitted(action)
+                && isActionArgumentsValid(action, arguments);
         }
 
         public boolean canRetrieveWindowContent(Service service) {
@@ -1779,6 +1790,29 @@
              return (VALID_ACTIONS & action) != 0;
         }
 
+        private boolean isActionArgumentsValid(int action, Bundle arguments) {
+            switch (action) {
+                case AccessibilityNodeInfo.ACTION_NEXT_AT_GRANULARITY:
+                case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_GRANULARITY: {
+                    if (arguments.size() == 1) {
+                        final int granularity = arguments.getInt(
+                                AccessibilityNodeInfo.ACTION_ARGUMENT_GRANULARITY_INT);
+                        return (granularity & VALID_GRANULARITIES) != 0
+                                && Integer.bitCount(granularity) == 1;
+                    }
+                } break;
+                case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
+                case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: {
+                    if (arguments.size() == 1) {
+                        String element = arguments.getString(
+                                AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING);
+                        return !TextUtils.isEmpty(element);
+                    }
+                } break;
+            }
+            return false;
+        }
+
         private void enforceCallingPermission(String permission, String function) {
             if (OWN_PROCESS_ID == Binder.getCallingPid()) {
                 return;