Skip to content

Commit e5dfa47

Browse files
committed
Improving accessibility focus traversal.
1. Now the views considered during the accessibility focus search are the ones that would get accessibility focus when thovered over. This way the user will get the same items i.e. feedback if he touch explores the screen and uses focus traversal. This is imperative for a good user experience. 2. Updated which focusables are considered when searching for access focus in ViewGroup. Generally accessibility focus ignores focus before/after descendants. 3. Implemented focus search strategy in AbsListView that will traverse the items of the current list (and the stuff withing one item before moving to the next) before continuing the search if forward and backward accessibility focus direction. 4. View focus search stops at root namespace. This is not the right way to prevent some stuff that is not supposed to get a focus in a container for a specific state. Actually the addFocusables for that container has to be overriden. Further this approach leads to focus getting stuck. The accessibility focus ignores root names space since we want to traverse the entire screen. 5. Fixed an bug in AccessibilityInteractionController which was not starting to search from the root of a virtual node tree. 6. Fixed a couple of bugs in FocusFinder where it was possible to get index out of bounds exception if the focusables list is empty. bug:5932640 Change-Id: Ic3bdd11767a7d40fbb21f35dcd79a4746af784d4
1 parent 0d607fb commit e5dfa47

File tree

8 files changed

+209
-87
lines changed

8 files changed

+209
-87
lines changed

core/java/android/view/AccessibilityInteractionController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ private void findAccessibilityNodeInfosByTextUiThread(Message message) {
330330
if (provider != null) {
331331
List<AccessibilityNodeInfo> infosFromProvider =
332332
provider.findAccessibilityNodeInfosByText(text,
333-
virtualDescendantId);
333+
AccessibilityNodeInfo.UNDEFINED);
334334
if (infosFromProvider != null) {
335335
infos.addAll(infosFromProvider);
336336
}

core/java/android/view/FocusFinder.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,10 @@ private static View getNextFocusable(View focused, ArrayList<View> focusables, i
276276
return focusables.get(position + 1);
277277
}
278278
}
279-
return focusables.get(0);
279+
if (!focusables.isEmpty()) {
280+
return focusables.get(0);
281+
}
282+
return null;
280283
}
281284

282285
private static View getBackwardFocusable(ViewGroup root, View focused,
@@ -293,7 +296,10 @@ private static View getPreviousFocusable(View focused, ArrayList<View> focusable
293296
return focusables.get(position - 1);
294297
}
295298
}
296-
return focusables.get(count - 1);
299+
if (!focusables.isEmpty()) {
300+
return focusables.get(count - 1);
301+
}
302+
return null;
297303
}
298304

299305
/**

core/java/android/view/View.java

Lines changed: 24 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6029,8 +6029,7 @@ public void addFocusables(ArrayList<View> views, int direction, int focusableMod
60296029
return;
60306030
}
60316031
if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
6032-
if (AccessibilityManager.getInstance(mContext).isEnabled()
6033-
&& includeForAccessibility()) {
6032+
if (canTakeAccessibilityFocusFromHover()) {
60346033
views.add(this);
60356034
return;
60366035
}
@@ -6183,57 +6182,28 @@ public void clearAccessibilityFocus() {
61836182
}
61846183
}
61856184

6186-
/**
6187-
* Find the best view to take accessibility focus from a hover.
6188-
* This function finds the deepest actionable view and if that
6189-
* fails ask the parent to take accessibility focus from hover.
6190-
*
6191-
* @param x The X hovered location in this view coorditantes.
6192-
* @param y The Y hovered location in this view coorditantes.
6193-
* @return Whether the request was handled.
6194-
*
6195-
* @hide
6196-
*/
6197-
public boolean requestAccessibilityFocusFromHover(float x, float y) {
6198-
if (onRequestAccessibilityFocusFromHover(x, y)) {
6199-
return true;
6200-
}
6201-
ViewParent parent = mParent;
6202-
if (parent instanceof View) {
6203-
View parentView = (View) parent;
6204-
6205-
float[] position = mAttachInfo.mTmpTransformLocation;
6206-
position[0] = x;
6207-
position[1] = y;
6208-
6209-
// Compensate for the transformation of the current matrix.
6210-
if (!hasIdentityMatrix()) {
6211-
getMatrix().mapPoints(position);
6185+
private void requestAccessibilityFocusFromHover() {
6186+
if (includeForAccessibility() && isActionableForAccessibility()) {
6187+
requestAccessibilityFocus();
6188+
} else {
6189+
if (mParent != null) {
6190+
View nextFocus = mParent.findViewToTakeAccessibilityFocusFromHover(this, this);
6191+
if (nextFocus != null) {
6192+
nextFocus.requestAccessibilityFocus();
6193+
}
62126194
}
6213-
6214-
// Compensate for the parent scroll and the offset
6215-
// of this view stop from the parent top.
6216-
position[0] += mLeft - parentView.mScrollX;
6217-
position[1] += mTop - parentView.mScrollY;
6218-
6219-
return parentView.requestAccessibilityFocusFromHover(position[0], position[1]);
62206195
}
6221-
return false;
62226196
}
62236197

62246198
/**
6225-
* Requests to give this View focus from hover.
6226-
*
6227-
* @param x The X hovered location in this view coorditantes.
6228-
* @param y The Y hovered location in this view coorditantes.
6229-
* @return Whether the request was handled.
6230-
*
62316199
* @hide
62326200
*/
6233-
public boolean onRequestAccessibilityFocusFromHover(float x, float y) {
6234-
if (includeForAccessibility()
6235-
&& (isActionableForAccessibility() || hasListenersForAccessibility())) {
6236-
return requestAccessibilityFocus();
6201+
public boolean canTakeAccessibilityFocusFromHover() {
6202+
if (includeForAccessibility() && isActionableForAccessibility()) {
6203+
return true;
6204+
}
6205+
if (mParent != null) {
6206+
return (mParent.findViewToTakeAccessibilityFocusFromHover(this, this) == this);
62376207
}
62386208
return false;
62396209
}
@@ -6495,14 +6465,15 @@ public void addChildrenForAccessibility(ArrayList<View> children) {
64956465
* important for accessibility are regarded.
64966466
*
64976467
* @return Whether to regard the view for accessibility.
6468+
*
6469+
* @hide
64986470
*/
6499-
boolean includeForAccessibility() {
6471+
public boolean includeForAccessibility() {
65006472
if (mAttachInfo != null) {
65016473
if (!mAttachInfo.mIncludeNotImportantViews) {
65026474
return isImportantForAccessibility();
6503-
} else {
6504-
return true;
65056475
}
6476+
return true;
65066477
}
65076478
return false;
65086479
}
@@ -6513,8 +6484,10 @@ boolean includeForAccessibility() {
65136484
* accessiiblity.
65146485
*
65156486
* @return True if the view is actionable for accessibility.
6487+
*
6488+
* @hide
65166489
*/
6517-
private boolean isActionableForAccessibility() {
6490+
public boolean isActionableForAccessibility() {
65186491
return (isClickable() || isLongClickable() || isFocusable());
65196492
}
65206493

@@ -7687,7 +7660,7 @@ public boolean onHoverEvent(MotionEvent event) {
76877660
&& pointInView(event.getX(), event.getY())) {
76887661
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
76897662
mSendingHoverAccessibilityEvents = true;
7690-
requestAccessibilityFocusFromHover((int) event.getX(), (int) event.getY());
7663+
requestAccessibilityFocusFromHover();
76917664
}
76927665
} else {
76937666
if (action == MotionEvent.ACTION_HOVER_EXIT

core/java/android/view/ViewGroup.java

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,11 @@ public ActionMode startActionModeForChild(View originalView, ActionMode.Callback
628628
* FOCUS_RIGHT, or 0 for not applicable.
629629
*/
630630
public View focusSearch(View focused, int direction) {
631-
if (isRootNamespace()) {
631+
// If we are moving accessibility focus we want to consider all
632+
// views no matter if they are on the screen. It is responsibility
633+
// of the accessibility service to check whether the result is in
634+
// the screen.
635+
if (isRootNamespace() && (direction & FOCUS_ACCESSIBILITY) == 0) {
632636
// root namespace means we should consider ourselves the top of the
633637
// tree for focus searching; otherwise we could be focus searching
634638
// into other tabs. see LocalActivityManager and TabHost for more info
@@ -853,14 +857,6 @@ public boolean hasFocusable() {
853857
return false;
854858
}
855859

856-
/**
857-
* {@inheritDoc}
858-
*/
859-
@Override
860-
public void addFocusables(ArrayList<View> views, int direction) {
861-
addFocusables(views, direction, FOCUSABLES_TOUCH_MODE);
862-
}
863-
864860
/**
865861
* {@inheritDoc}
866862
*/
@@ -870,7 +866,8 @@ public void addFocusables(ArrayList<View> views, int direction, int focusableMod
870866

871867
final int descendantFocusability = getDescendantFocusability();
872868

873-
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
869+
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS
870+
|| (focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
874871
final int count = mChildrenCount;
875872
final View[] children = mChildren;
876873

@@ -886,10 +883,11 @@ public void addFocusables(ArrayList<View> views, int direction, int focusableMod
886883
// FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
887884
// to avoid the focus search finding layouts when a more precise search
888885
// among the focusable children would be more interesting.
889-
if (
890-
descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
886+
if (descendantFocusability != FOCUS_AFTER_DESCENDANTS
891887
// No focusable descendants
892-
(focusableCount == views.size())) {
888+
|| (focusableCount == views.size())
889+
// We are collecting accessibility focusables.
890+
|| (focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
893891
super.addFocusables(views, direction, focusableMode);
894892
}
895893
}
@@ -1659,6 +1657,20 @@ public void childAccessibilityStateChanged(View child) {
16591657
}
16601658
}
16611659

1660+
/**
1661+
* @hide
1662+
*/
1663+
@Override
1664+
public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) {
1665+
if (includeForAccessibility() && isActionableForAccessibility()) {
1666+
return this;
1667+
}
1668+
if (mParent != null) {
1669+
return mParent.findViewToTakeAccessibilityFocusFromHover(this, descendant);
1670+
}
1671+
return null;
1672+
}
1673+
16621674
/**
16631675
* Implement this method to intercept hover events before they are handled
16641676
* by child views.

core/java/android/view/ViewParent.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,16 @@ public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
295295
* @hide
296296
*/
297297
public void childAccessibilityStateChanged(View child);
298+
299+
/**
300+
* A descendant requests this view to find a candidate to take accessibility
301+
* focus from hover.
302+
*
303+
* @param child The child making the call.
304+
* @param descendant The descendant that made the initial request.
305+
* @return A view to take accessibility focus.
306+
*
307+
* @hide
308+
*/
309+
public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant);
298310
}

core/java/android/view/ViewRootImpl.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2340,6 +2340,14 @@ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
23402340
return true;
23412341
}
23422342

2343+
@Override
2344+
public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) {
2345+
if (descendant.includeForAccessibility()) {
2346+
return descendant;
2347+
}
2348+
return null;
2349+
}
2350+
23432351
/**
23442352
* We want to draw a highlight around the current accessibility focused.
23452353
* Since adding a style for all possible view is not a viable option we
@@ -2535,6 +2543,20 @@ boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
25352543
return handled;
25362544
}
25372545

2546+
/**
2547+
* @hide
2548+
*/
2549+
public View getAccessibilityFocusedHost() {
2550+
return mAccessibilityFocusedHost;
2551+
}
2552+
2553+
/**
2554+
* @hide
2555+
*/
2556+
public AccessibilityNodeInfo getAccessibilityFocusedVirtualView() {
2557+
return mAccessibilityFocusedVirtualView;
2558+
}
2559+
25382560
void setAccessibilityFocusedHost(View host) {
25392561
if (mAccessibilityFocusedHost != null && mAccessibilityFocusedVirtualView == null) {
25402562
mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks();
@@ -2687,7 +2709,7 @@ void updateConfiguration(Configuration config, boolean force) {
26872709
/**
26882710
* Return true if child is an ancestor of parent, (or equal to the parent).
26892711
*/
2690-
static boolean isViewDescendantOf(View child, View parent) {
2712+
public static boolean isViewDescendantOf(View child, View parent) {
26912713
if (child == parent) {
26922714
return true;
26932715
}

0 commit comments

Comments
 (0)