Merge "Improving accessibility focus traversal." into jb-dev
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index d42757d..e1f1db2 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -330,7 +330,7 @@
if (provider != null) {
List<AccessibilityNodeInfo> infosFromProvider =
provider.findAccessibilityNodeInfosByText(text,
- virtualDescendantId);
+ AccessibilityNodeInfo.UNDEFINED);
if (infosFromProvider != null) {
infos.addAll(infosFromProvider);
}
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 6bf1888..9063cea 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -276,7 +276,10 @@
return focusables.get(position + 1);
}
}
- return focusables.get(0);
+ if (!focusables.isEmpty()) {
+ return focusables.get(0);
+ }
+ return null;
}
private static View getBackwardFocusable(ViewGroup root, View focused,
@@ -293,7 +296,10 @@
return focusables.get(position - 1);
}
}
- return focusables.get(count - 1);
+ if (!focusables.isEmpty()) {
+ return focusables.get(count - 1);
+ }
+ return null;
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2f54615..14523d3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6027,8 +6027,7 @@
return;
}
if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()
- && includeForAccessibility()) {
+ if (canTakeAccessibilityFocusFromHover()) {
views.add(this);
return;
}
@@ -6181,57 +6180,28 @@
}
}
- /**
- * Find the best view to take accessibility focus from a hover.
- * This function finds the deepest actionable view and if that
- * fails ask the parent to take accessibility focus from hover.
- *
- * @param x The X hovered location in this view coorditantes.
- * @param y The Y hovered location in this view coorditantes.
- * @return Whether the request was handled.
- *
- * @hide
- */
- public boolean requestAccessibilityFocusFromHover(float x, float y) {
- if (onRequestAccessibilityFocusFromHover(x, y)) {
- return true;
- }
- ViewParent parent = mParent;
- if (parent instanceof View) {
- View parentView = (View) parent;
-
- float[] position = mAttachInfo.mTmpTransformLocation;
- position[0] = x;
- position[1] = y;
-
- // Compensate for the transformation of the current matrix.
- if (!hasIdentityMatrix()) {
- getMatrix().mapPoints(position);
+ private void requestAccessibilityFocusFromHover() {
+ if (includeForAccessibility() && isActionableForAccessibility()) {
+ requestAccessibilityFocus();
+ } else {
+ if (mParent != null) {
+ View nextFocus = mParent.findViewToTakeAccessibilityFocusFromHover(this, this);
+ if (nextFocus != null) {
+ nextFocus.requestAccessibilityFocus();
+ }
}
-
- // Compensate for the parent scroll and the offset
- // of this view stop from the parent top.
- position[0] += mLeft - parentView.mScrollX;
- position[1] += mTop - parentView.mScrollY;
-
- return parentView.requestAccessibilityFocusFromHover(position[0], position[1]);
}
- return false;
}
/**
- * Requests to give this View focus from hover.
- *
- * @param x The X hovered location in this view coorditantes.
- * @param y The Y hovered location in this view coorditantes.
- * @return Whether the request was handled.
- *
* @hide
*/
- public boolean onRequestAccessibilityFocusFromHover(float x, float y) {
- if (includeForAccessibility()
- && (isActionableForAccessibility() || hasListenersForAccessibility())) {
- return requestAccessibilityFocus();
+ public boolean canTakeAccessibilityFocusFromHover() {
+ if (includeForAccessibility() && isActionableForAccessibility()) {
+ return true;
+ }
+ if (mParent != null) {
+ return (mParent.findViewToTakeAccessibilityFocusFromHover(this, this) == this);
}
return false;
}
@@ -6493,14 +6463,15 @@
* important for accessibility are regarded.
*
* @return Whether to regard the view for accessibility.
+ *
+ * @hide
*/
- boolean includeForAccessibility() {
+ public boolean includeForAccessibility() {
if (mAttachInfo != null) {
if (!mAttachInfo.mIncludeNotImportantViews) {
return isImportantForAccessibility();
- } else {
- return true;
}
+ return true;
}
return false;
}
@@ -6511,8 +6482,10 @@
* accessiiblity.
*
* @return True if the view is actionable for accessibility.
+ *
+ * @hide
*/
- private boolean isActionableForAccessibility() {
+ public boolean isActionableForAccessibility() {
return (isClickable() || isLongClickable() || isFocusable());
}
@@ -7688,7 +7661,7 @@
&& pointInView(event.getX(), event.getY())) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
mSendingHoverAccessibilityEvents = true;
- requestAccessibilityFocusFromHover((int) event.getX(), (int) event.getY());
+ requestAccessibilityFocusFromHover();
}
} else {
if (action == MotionEvent.ACTION_HOVER_EXIT
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f55b7ac..b95ca5e 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -628,7 +628,11 @@
* FOCUS_RIGHT, or 0 for not applicable.
*/
public View focusSearch(View focused, int direction) {
- if (isRootNamespace()) {
+ // If we are moving accessibility focus we want to consider all
+ // views no matter if they are on the screen. It is responsibility
+ // of the accessibility service to check whether the result is in
+ // the screen.
+ if (isRootNamespace() && (direction & FOCUS_ACCESSIBILITY) == 0) {
// root namespace means we should consider ourselves the top of the
// tree for focus searching; otherwise we could be focus searching
// into other tabs. see LocalActivityManager and TabHost for more info
@@ -857,20 +861,13 @@
* {@inheritDoc}
*/
@Override
- public void addFocusables(ArrayList<View> views, int direction) {
- addFocusables(views, direction, FOCUSABLES_TOUCH_MODE);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
- if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
+ if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS
+ || (focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
final int count = mChildrenCount;
final View[] children = mChildren;
@@ -886,10 +883,11 @@
// FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
// to avoid the focus search finding layouts when a more precise search
// among the focusable children would be more interesting.
- if (
- descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
+ if (descendantFocusability != FOCUS_AFTER_DESCENDANTS
// No focusable descendants
- (focusableCount == views.size())) {
+ || (focusableCount == views.size())
+ // We are collecting accessibility focusables.
+ || (focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
super.addFocusables(views, direction, focusableMode);
}
}
@@ -1659,6 +1657,20 @@
}
/**
+ * @hide
+ */
+ @Override
+ public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) {
+ if (includeForAccessibility() && isActionableForAccessibility()) {
+ return this;
+ }
+ if (mParent != null) {
+ return mParent.findViewToTakeAccessibilityFocusFromHover(this, descendant);
+ }
+ return null;
+ }
+
+ /**
* Implement this method to intercept hover events before they are handled
* by child views.
* <p>
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index ddff91d..d93b996 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -295,4 +295,16 @@
* @hide
*/
public void childAccessibilityStateChanged(View child);
+
+ /**
+ * A descendant requests this view to find a candidate to take accessibility
+ * focus from hover.
+ *
+ * @param child The child making the call.
+ * @param descendant The descendant that made the initial request.
+ * @return A view to take accessibility focus.
+ *
+ * @hide
+ */
+ public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 90e6034..f86e036 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2325,6 +2325,14 @@
return true;
}
+ @Override
+ public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) {
+ if (descendant.includeForAccessibility()) {
+ return descendant;
+ }
+ return null;
+ }
+
/**
* We want to draw a highlight around the current accessibility focused.
* Since adding a style for all possible view is not a viable option we
@@ -2520,6 +2528,20 @@
return handled;
}
+ /**
+ * @hide
+ */
+ public View getAccessibilityFocusedHost() {
+ return mAccessibilityFocusedHost;
+ }
+
+ /**
+ * @hide
+ */
+ public AccessibilityNodeInfo getAccessibilityFocusedVirtualView() {
+ return mAccessibilityFocusedVirtualView;
+ }
+
void setAccessibilityFocusedHost(View host) {
if (mAccessibilityFocusedHost != null && mAccessibilityFocusedVirtualView == null) {
mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks();
@@ -2672,7 +2694,7 @@
/**
* Return true if child is an ancestor of parent, (or equal to the parent).
*/
- static boolean isViewDescendantOf(View child, View parent) {
+ public static boolean isViewDescendantOf(View child, View parent) {
if (child == parent) {
return true;
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 3b4ec7d..ab9d370 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -42,6 +42,7 @@
import android.util.StateSet;
import android.view.ActionMode;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.FocusFinder;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
@@ -56,6 +57,7 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -1327,6 +1329,119 @@
}
@Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY
+ && (direction == ACCESSIBILITY_FOCUS_FORWARD
+ || direction == ACCESSIBILITY_FOCUS_BACKWARD)) {
+ if (canTakeAccessibilityFocusFromHover()) {
+ views.add(this);
+ }
+ } else {
+ super.addFocusables(views, direction, focusableMode);
+ }
+ }
+
+ @Override
+ public View focusSearch(int direction) {
+ return focusSearch(null, direction);
+ }
+
+ @Override
+ public View focusSearch(View focused, int direction) {
+ switch (direction) {
+ case ACCESSIBILITY_FOCUS_FORWARD: {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ break;
+ }
+ View currentFocus = viewRootImpl.getAccessibilityFocusedHost();
+ if (currentFocus == null) {
+ break;
+ }
+ // If we have the focus try giving it to the first child.
+ if (currentFocus == this) {
+ final int firstVisiblePosition = getFirstVisiblePosition();
+ if (firstVisiblePosition >= 0) {
+ return getChildAt(0);
+ }
+ return null;
+ }
+ // Find the item that has accessibility focus.
+ final int currentPosition = getPositionForView(currentFocus);
+ if (currentPosition < 0 || currentPosition >= getCount()) {
+ break;
+ }
+ // Try to advance focus in the current item.
+ View currentItem = getChildAt(currentPosition - getFirstVisiblePosition());
+ if (currentItem instanceof ViewGroup) {
+ ViewGroup currentItemGroup = (ViewGroup) currentItem;
+ View nextFocus = FocusFinder.getInstance().findNextFocus(currentItemGroup,
+ currentFocus, direction);
+ if (nextFocus != null && nextFocus != currentItemGroup
+ && nextFocus != currentFocus) {
+ return nextFocus;
+ }
+ }
+ // Try to move focus to the next item.
+ final int nextPosition = currentPosition - getFirstVisiblePosition() + 1;
+ if (nextPosition < getChildCount()) {
+ return getChildAt(nextPosition);
+ }
+ } break;
+ case ACCESSIBILITY_FOCUS_BACKWARD: {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ break;
+ }
+ View currentFocus = viewRootImpl.getAccessibilityFocusedHost();
+ if (currentFocus == null) {
+ break;
+ }
+ // If we have the focus do a generic search.
+ if (currentFocus == this) {
+ return super.focusSearch(this, direction);
+ }
+ // Find the item that has accessibility focus.
+ final int currentPosition = getPositionForView(currentFocus);
+ if (currentPosition < 0 || currentPosition >= getCount()) {
+ break;
+ }
+ // Try to advance focus in the current item.
+ View currentItem = getChildAt(currentPosition - getFirstVisiblePosition());
+ if (currentItem instanceof ViewGroup) {
+ ViewGroup currentItemGroup = (ViewGroup) currentItem;
+ View nextFocus = FocusFinder.getInstance().findNextFocus(currentItemGroup,
+ currentFocus, direction);
+ if (nextFocus != null && nextFocus != currentItemGroup
+ && nextFocus != currentFocus) {
+ return nextFocus;
+ }
+ }
+ // Try to move focus to the previous item.
+ final int nextPosition = currentPosition - getFirstVisiblePosition() - 1;
+ if (nextPosition >= 0) {
+ return getChildAt(nextPosition);
+ } else {
+ return this;
+ }
+ }
+ }
+ return super.focusSearch(focused, direction);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) {
+ final int position = getPositionForView(child);
+ if (position != INVALID_POSITION) {
+ return getChildAt(position - mFirstPosition);
+ }
+ return super.findViewToTakeAccessibilityFocusFromHover(child, descendant);
+ }
+
+ @Override
public void sendAccessibilityEvent(int eventType) {
// Since this class calls onScrollChanged even if the mFirstPosition and the
// child count have not changed we will avoid sending duplicate accessibility
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index abfc577..502de31 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -24,7 +24,6 @@
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
@@ -32,6 +31,7 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
/**
* An AdapterView is a view whose children are determined by an {@link Adapter}.
@@ -957,24 +957,6 @@
event.setItemCount(getCount());
}
- /**
- * @hide
- */
- @Override
- public boolean onRequestAccessibilityFocusFromHover(float x, float y) {
- // We prefer to five focus to the child instead of this view.
- // Usually the children are not actionable for accessibility,
- // and they will not take accessibility focus, so we give it.
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (isTransformedTouchPointInView(x, y, child, null)) {
- return child.requestAccessibilityFocus();
- }
- }
- return super.onRequestAccessibilityFocusFromHover(x, y);
- }
-
private boolean isScrollableForAccessibility() {
T adapter = getAdapter();
if (adapter != null) {