am 8ce2d78a: Merge "Improving accessibility focus traversal." into jb-dev
* commit '8ce2d78aa89e89e9a5607d8809bf6d248508a531': Improving accessibility focus traversal.
This commit is contained in:
@@ -330,7 +330,7 @@ final class AccessibilityInteractionController {
|
||||
if (provider != null) {
|
||||
List<AccessibilityNodeInfo> infosFromProvider =
|
||||
provider.findAccessibilityNodeInfosByText(text,
|
||||
virtualDescendantId);
|
||||
AccessibilityNodeInfo.UNDEFINED);
|
||||
if (infosFromProvider != null) {
|
||||
infos.addAll(infosFromProvider);
|
||||
}
|
||||
|
||||
@@ -276,7 +276,10 @@ public class FocusFinder {
|
||||
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 @@ public class FocusFinder {
|
||||
return focusables.get(position - 1);
|
||||
}
|
||||
}
|
||||
return focusables.get(count - 1);
|
||||
if (!focusables.isEmpty()) {
|
||||
return focusables.get(count - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6027,8 +6027,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
|
||||
return;
|
||||
}
|
||||
if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
|
||||
if (AccessibilityManager.getInstance(mContext).isEnabled()
|
||||
&& includeForAccessibility()) {
|
||||
if (canTakeAccessibilityFocusFromHover()) {
|
||||
views.add(this);
|
||||
return;
|
||||
}
|
||||
@@ -6181,57 +6180,28 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
|
||||
* 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 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
|
||||
* 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 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
|
||||
&& 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
|
||||
|
||||
@@ -628,7 +628,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
* 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
|
||||
@@ -853,14 +857,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void addFocusables(ArrayList<View> views, int direction) {
|
||||
addFocusables(views, direction, FOCUSABLES_TOUCH_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -870,7 +866,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
|
||||
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 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1658,6 +1656,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
|
||||
@@ -295,4 +295,16 @@ public interface ViewParent {
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -2325,6 +2325,14 @@ public final class ViewRootImpl implements ViewParent,
|
||||
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 @@ public final class ViewRootImpl implements ViewParent,
|
||||
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 @@ public final class ViewRootImpl implements ViewParent,
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import android.util.SparseBooleanArray;
|
||||
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.ViewConfiguration;
|
||||
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;
|
||||
@@ -1326,6 +1328,119 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.util.AttributeSet;
|
||||
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.ViewGroup;
|
||||
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 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user