Adding accessibility focusable attribute (hidden for now).

1. This attribute specifies whether a view can take accessibility
   focus. It has three values: 1) auto - the system determines
   based on whether the view is actionable and has actionable
   predecessor. Accessibility services can put accessibility focus
   on such a node at will; 2) yes ; this view always takes access
   focus; 3) no - the view cannot takes accessibility focus and
   accessibility services cannot put accessibility focus on it.

Change-Id: I2ebf4e7c75bf6b39e1742b6868b37ccdd4cc7d28
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 20eef11..b6b4c96 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2086,7 +2086,8 @@
     // Accessiblity constants for mPrivateFlags2
 
     /**
-     * Shift for accessibility related bits in {@link #mPrivateFlags2}.
+     * Shift for the bits in {@link #mPrivateFlags2} related to the
+     * "importantForAccessibility" attribute.
      */
     static final int IMPORTANT_FOR_ACCESSIBILITY_SHIFT = 20;
 
@@ -2142,6 +2143,72 @@
      */
     static final int VIEW_QUICK_REJECTED = 0x20000000;
 
+    // Accessiblity constants for mPrivateFlags2
+
+    /**
+     * Shift for the bits in {@link #mPrivateFlags2} related to the
+     * "accessibilityFocusable" attribute.
+     */
+    static final int ACCESSIBILITY_FOCUSABLE_SHIFT = 30;
+
+    /**
+     * The system determines whether the view can take accessibility focus - default (recommended).
+     * <p>
+     * Such a view is consideted by the focus search if it is:
+     * <ul>
+     * <li>
+     * Important for accessibility and actionable (clickable, long clickable, focusable)
+     * </li>
+     * <li>
+     * Important for accessibility, not actionable (clickable, long clickable, focusable),
+     * and does not have an actionable predecessor.
+     * </li>
+     * </ul>
+     * An accessibility srvice can request putting accessibility focus on such a view.
+     * </p>
+     *
+     * @hide
+     */
+    public static final int ACCESSIBILITY_FOCUSABLE_AUTO = 0x00000000;
+
+    /**
+     * The view can take accessibility focus.
+     * <p>
+     * A view that can take accessibility focus is always considered during focus
+     * search and an accessibility service can request putting accessibility focus
+     * on it.
+     * </p>
+     *
+     * @hide
+     */
+    public static final int ACCESSIBILITY_FOCUSABLE_YES = 0x00000001;
+
+    /**
+     * The view can not take accessibility focus.
+     * <p>
+     * A view that can not take accessibility focus is never considered during focus
+     * search and an accessibility service can not request putting accessibility focus
+     * on it.
+     * </p>
+     *
+     * @hide
+     */
+    public static final int ACCESSIBILITY_FOCUSABLE_NO = 0x00000002;
+
+    /**
+     * The default whether the view is accessiblity focusable.
+     */
+    static final int ACCESSIBILITY_FOCUSABLE_DEFAULT = ACCESSIBILITY_FOCUSABLE_AUTO;
+
+    /**
+     * Mask for obtainig the bits which specifies how to determine
+     * whether a view is accessibility focusable.
+     */
+    static final int ACCESSIBILITY_FOCUSABLE_MASK = (ACCESSIBILITY_FOCUSABLE_AUTO
+        | ACCESSIBILITY_FOCUSABLE_YES | ACCESSIBILITY_FOCUSABLE_NO)
+        << ACCESSIBILITY_FOCUSABLE_SHIFT;
+
+
     /* End of masks for mPrivateFlags2 */
 
     static final int DRAG_MASK = DRAG_CAN_ACCEPT | DRAG_HOVERED;
@@ -3132,7 +3199,8 @@
         mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) |
                 (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT) |
                 (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT) |
-                (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
+                (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << IMPORTANT_FOR_ACCESSIBILITY_SHIFT) |
+                (ACCESSIBILITY_FOCUSABLE_DEFAULT << ACCESSIBILITY_FOCUSABLE_SHIFT);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
         mUserPaddingStart = -1;
@@ -4788,7 +4856,10 @@
         }
 
         if (!isAccessibilityFocused()) {
-            info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            final int mode = getAccessibilityFocusable();
+            if (mode == ACCESSIBILITY_FOCUSABLE_YES || mode == ACCESSIBILITY_FOCUSABLE_AUTO) {
+                info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            }
         } else {
             info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
         }
@@ -6069,7 +6140,7 @@
             return;
         }
         if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
-            if (canTakeAccessibilityFocusFromHover() || getAccessibilityNodeProvider() != null) {
+            if (isAccessibilityFocusable()) {
                 views.add(this);
                 return;
             }
@@ -6403,12 +6474,9 @@
      * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
      */
     @ViewDebug.ExportedProperty(category = "accessibility", mapping = {
-            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_AUTO,
-                    to = "IMPORTANT_FOR_ACCESSIBILITY_AUTO"),
-            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_YES,
-                    to = "IMPORTANT_FOR_ACCESSIBILITY_YES"),
-            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO,
-                    to = "IMPORTANT_FOR_ACCESSIBILITY_NO")
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_AUTO, to = "auto"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_YES, to = "yes"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO, to = "no")
         })
     public int getImportantForAccessibility() {
         return (mPrivateFlags2 & IMPORTANT_FOR_ACCESSIBILITY_MASK)
@@ -6461,6 +6529,73 @@
     }
 
     /**
+     * Gets the mode for determining whether this View can take accessibility focus.
+     *
+     * @return The mode for determining whether a View can take accessibility focus.
+     *
+     * @attr ref android.R.styleable#View_accessibilityFocusable
+     *
+     * @see #ACCESSIBILITY_FOCUSABLE_YES
+     * @see #ACCESSIBILITY_FOCUSABLE_NO
+     * @see #ACCESSIBILITY_FOCUSABLE_AUTO
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "accessibility", mapping = {
+            @ViewDebug.IntToString(from = ACCESSIBILITY_FOCUSABLE_AUTO, to = "auto"),
+            @ViewDebug.IntToString(from = ACCESSIBILITY_FOCUSABLE_YES, to = "yes"),
+            @ViewDebug.IntToString(from = ACCESSIBILITY_FOCUSABLE_NO, to = "no")
+        })
+    public int getAccessibilityFocusable() {
+        return (mPrivateFlags2 & ACCESSIBILITY_FOCUSABLE_MASK) >>> ACCESSIBILITY_FOCUSABLE_SHIFT;
+    }
+
+    /**
+     * Sets how to determine whether this view can take accessibility focus.
+     *
+     * @param mode How to determine whether this view can take accessibility focus.
+     *
+     * @attr ref android.R.styleable#View_accessibilityFocusable
+     *
+     * @see #ACCESSIBILITY_FOCUSABLE_YES
+     * @see #ACCESSIBILITY_FOCUSABLE_NO
+     * @see #ACCESSIBILITY_FOCUSABLE_AUTO
+     *
+     * @hide
+     */
+    public void setAccessibilityFocusable(int mode) {
+        if (mode != getAccessibilityFocusable()) {
+            mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSABLE_MASK;
+            mPrivateFlags2 |= (mode << ACCESSIBILITY_FOCUSABLE_SHIFT)
+                    & ACCESSIBILITY_FOCUSABLE_MASK;
+            notifyAccessibilityStateChanged();
+        }
+    }
+
+    /**
+     * Gets whether this view can take accessibility focus.
+     *
+     * @return Whether the view can take accessibility focus.
+     *
+     * @hide
+     */
+    public boolean isAccessibilityFocusable() {
+        final int mode = (mPrivateFlags2 & ACCESSIBILITY_FOCUSABLE_MASK)
+                >>> ACCESSIBILITY_FOCUSABLE_SHIFT;
+        switch (mode) {
+            case ACCESSIBILITY_FOCUSABLE_YES:
+                return true;
+            case ACCESSIBILITY_FOCUSABLE_NO:
+                return false;
+            case ACCESSIBILITY_FOCUSABLE_AUTO:
+                return canTakeAccessibilityFocusFromHover()
+                        || getAccessibilityNodeProvider() != null;
+            default:
+                throw new IllegalArgumentException("Unknow accessibility focusable mode: " + mode);
+        }
+    }
+
+    /**
      * Gets the parent for accessibility purposes. Note that the parent for
      * accessibility is not necessary the immediate parent. It is the first
      * predecessor that is important for accessibility.
@@ -6641,7 +6776,10 @@
                 }
             } break;
             case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
-                if (!isAccessibilityFocused()) {
+                final int mode = getAccessibilityFocusable();
+                if (!isAccessibilityFocused()
+                        && (mode == ACCESSIBILITY_FOCUSABLE_YES
+                                || mode == ACCESSIBILITY_FOCUSABLE_AUTO)) {
                     return requestAccessibilityFocus();
                 }
             } break;