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;