am 8ce2d78a: Merge "Improving accessibility focus traversal." into jb-dev

* commit '8ce2d78aa89e89e9a5607d8809bf6d248508a531':
  Improving accessibility focus traversal.
This commit is contained in:
Svetoslav Ganov
2012-05-15 11:31:28 -07:00
committed by Android Git Automerger
8 changed files with 209 additions and 87 deletions

View File

@@ -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);
}

View File

@@ -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;
}
/**

View File

@@ -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

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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) {