FileDocCategorySizeDatePackage
ExploreByTouchHelper.javaAPI DocAndroid 5.1 API29429Thu Mar 12 22:22:10 GMT 2015com.android.internal.widget

ExploreByTouchHelper

public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate
ExploreByTouchHelper is a utility class for implementing accessibility support in custom {@link android.view.View}s that represent a collection of View-like logical items. It extends {@link android.view.accessibility.AccessibilityNodeProvider} and simplifies many aspects of providing information to accessibility services and managing accessibility focus. This class does not currently support hierarchies of logical items.

This should be applied to the parent view using {@link android.view.View#setAccessibilityDelegate}:

mAccessHelper = ExploreByTouchHelper.create(someView, mAccessHelperCallback);
ViewCompat.setAccessibilityDelegate(someView, mAccessHelper);

Fields Summary
public static final int
INVALID_ID
Virtual node identifier value for invalid nodes.
private static final String
DEFAULT_CLASS_NAME
Default class name used for virtual views.
private static final android.graphics.Rect
INVALID_PARENT_BOUNDS
Default bounds used to determine if the client didn't set any.
private android.graphics.Rect
mTempScreenRect
private android.graphics.Rect
mTempParentRect
private int[]
mTempGlobalRect
private android.graphics.Rect
mTempVisibleRect
Lazily-created temporary data structure used to compute visibility.
private android.util.IntArray
mTempArray
Lazily-created temporary data structure used to obtain child IDs.
private final android.view.accessibility.AccessibilityManager
mManager
System accessibility manager, used to check state and send events.
private final android.view.View
mView
View whose internal structure is exposed through this helper.
private final android.content.Context
mContext
Context of the host view.
private ExploreByTouchNodeProvider
mNodeProvider
Node provider that handles creating nodes and performing actions.
private int
mFocusedVirtualViewId
Virtual view id for the currently focused logical item.
private int
mHoveredVirtualViewId
Virtual view id for the currently hovered logical item.
Constructors Summary
public ExploreByTouchHelper(android.view.View forView)
Factory method to create a new {@link ExploreByTouchHelper}.

param
forView View whose logical children are exposed by this helper.


                            
       
        if (forView == null) {
            throw new IllegalArgumentException("View may not be null");
        }

        mView = forView;
        mContext = forView.getContext();
        mManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
    
Methods Summary
private booleanclearAccessibilityFocus(int virtualViewId)
Attempts to clear accessibility focus from a virtual view.

param
virtualViewId The id of the virtual view from which to clear accessibility focus.
return
Whether this virtual view actually cleared accessibility focus.

        if (isAccessibilityFocused(virtualViewId)) {
            mFocusedVirtualViewId = INVALID_ID;
            mView.invalidate();
            sendEventForVirtualView(virtualViewId,
                    AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
            return true;
        }
        return false;
    
private android.view.accessibility.AccessibilityEventcreateEvent(int virtualViewId, int eventType)
Constructs and returns an {@link AccessibilityEvent} for the specified virtual view id, which includes the host view ({@link View#NO_ID}).

param
virtualViewId The virtual view id for the item for which to construct an event.
param
eventType The type of event to construct.
return
An {@link AccessibilityEvent} populated with information about the specified item.

        switch (virtualViewId) {
            case View.NO_ID:
                return createEventForHost(eventType);
            default:
                return createEventForChild(virtualViewId, eventType);
        }
    
private android.view.accessibility.AccessibilityEventcreateEventForChild(int virtualViewId, int eventType)
Constructs and returns an {@link AccessibilityEvent} populated with information about the specified item.

param
virtualViewId The virtual view id for the item for which to construct an event.
param
eventType The type of event to construct.
return
An {@link AccessibilityEvent} populated with information about the specified item.

        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
        event.setEnabled(true);
        event.setClassName(DEFAULT_CLASS_NAME);

        // Allow the client to populate the event.
        onPopulateEventForVirtualView(virtualViewId, event);

        // Make sure the developer is following the rules.
        if (event.getText().isEmpty() && (event.getContentDescription() == null)) {
            throw new RuntimeException("Callbacks must add text or a content description in "
                    + "populateEventForVirtualViewId()");
        }

        // Don't allow the client to override these properties.
        event.setPackageName(mView.getContext().getPackageName());
        event.setSource(mView, virtualViewId);

        return event;
    
private android.view.accessibility.AccessibilityEventcreateEventForHost(int eventType)
Constructs and returns an {@link AccessibilityEvent} for the host node.

param
eventType The type of event to construct.
return
An {@link AccessibilityEvent} populated with information about the specified item.

        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
        onInitializeAccessibilityEvent(mView, event);
        return event;
    
private android.view.accessibility.AccessibilityNodeInfocreateNode(int virtualViewId)
Constructs and returns an {@link android.view.accessibility.AccessibilityNodeInfo} for the specified virtual view id, which includes the host view ({@link View#NO_ID}).

param
virtualViewId The virtual view id for the item for which to construct a node.
return
An {@link android.view.accessibility.AccessibilityNodeInfo} populated with information about the specified item.

        switch (virtualViewId) {
            case View.NO_ID:
                return createNodeForHost();
            default:
                return createNodeForChild(virtualViewId);
        }
    
private android.view.accessibility.AccessibilityNodeInfocreateNodeForChild(int virtualViewId)
Constructs and returns an {@link AccessibilityNodeInfo} for the specified item. Automatically manages accessibility focus actions.

Allows the implementing class to specify most node properties, but overrides the following:

  • {@link AccessibilityNodeInfo#setPackageName}
  • {@link AccessibilityNodeInfo#setClassName}
  • {@link AccessibilityNodeInfo#setParent(View)}
  • {@link AccessibilityNodeInfo#setSource(View, int)}
  • {@link AccessibilityNodeInfo#setVisibleToUser}
  • {@link AccessibilityNodeInfo#setBoundsInScreen(Rect)}

Uses the bounds of the parent view and the parent-relative bounding rectangle specified by {@link AccessibilityNodeInfo#getBoundsInParent} to automatically update the following properties:

  • {@link AccessibilityNodeInfo#setVisibleToUser}
  • {@link AccessibilityNodeInfo#setBoundsInParent}

param
virtualViewId The virtual view id for item for which to construct a node.
return
An {@link AccessibilityNodeInfo} for the specified item.

        ensureTempRects();
        final Rect tempParentRect = mTempParentRect;
        final int[] tempGlobalRect = mTempGlobalRect;
        final Rect tempScreenRect = mTempScreenRect;

        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();

        // Ensure the client has good defaults.
        node.setEnabled(true);
        node.setClassName(DEFAULT_CLASS_NAME);
        node.setBoundsInParent(INVALID_PARENT_BOUNDS);

        // Allow the client to populate the node.
        onPopulateNodeForVirtualView(virtualViewId, node);

        // Make sure the developer is following the rules.
        if ((node.getText() == null) && (node.getContentDescription() == null)) {
            throw new RuntimeException("Callbacks must add text or a content description in "
                    + "populateNodeForVirtualViewId()");
        }

        node.getBoundsInParent(tempParentRect);
        if (tempParentRect.equals(INVALID_PARENT_BOUNDS)) {
            throw new RuntimeException("Callbacks must set parent bounds in "
                    + "populateNodeForVirtualViewId()");
        }

        final int actions = node.getActions();
        if ((actions & AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) != 0) {
            throw new RuntimeException("Callbacks must not add ACTION_ACCESSIBILITY_FOCUS in "
                    + "populateNodeForVirtualViewId()");
        }
        if ((actions & AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) != 0) {
            throw new RuntimeException("Callbacks must not add ACTION_CLEAR_ACCESSIBILITY_FOCUS in "
                    + "populateNodeForVirtualViewId()");
        }

        // Don't allow the client to override these properties.
        node.setPackageName(mView.getContext().getPackageName());
        node.setSource(mView, virtualViewId);
        node.setParent(mView);

        // Manage internal accessibility focus state.
        if (mFocusedVirtualViewId == virtualViewId) {
            node.setAccessibilityFocused(true);
            node.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
        } else {
            node.setAccessibilityFocused(false);
            node.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
        }

        // Set the visibility based on the parent bound.
        if (intersectVisibleToUser(tempParentRect)) {
            node.setVisibleToUser(true);
            node.setBoundsInParent(tempParentRect);
        }

        // Calculate screen-relative bound.
        mView.getLocationOnScreen(tempGlobalRect);
        final int offsetX = tempGlobalRect[0];
        final int offsetY = tempGlobalRect[1];
        tempScreenRect.set(tempParentRect);
        tempScreenRect.offset(offsetX, offsetY);
        node.setBoundsInScreen(tempScreenRect);

        return node;
    
private android.view.accessibility.AccessibilityNodeInfocreateNodeForHost()
Constructs and returns an {@link AccessibilityNodeInfo} for the host view populated with its virtual descendants.

return
An {@link AccessibilityNodeInfo} for the parent node.

        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView);
        onInitializeAccessibilityNodeInfo(mView, node);

        // Add the virtual descendants.
        if (mTempArray == null) {
            mTempArray = new IntArray();
        } else {
            mTempArray.clear();
        }
        final IntArray virtualViewIds = mTempArray;
        getVisibleVirtualViews(virtualViewIds);

        final int N = virtualViewIds.size();
        for (int i = 0; i < N; i++) {
            node.addChild(mView, virtualViewIds.get(i));
        }

        return node;
    
public booleandispatchHoverEvent(android.view.MotionEvent event)
Dispatches hover {@link android.view.MotionEvent}s to the virtual view hierarchy when the Explore by Touch feature is enabled.

This method should be called by overriding {@link View#dispatchHoverEvent}:

@Override
public boolean dispatchHoverEvent(MotionEvent event) {
if (mHelper.dispatchHoverEvent(this, event) {
return true;
}
return super.dispatchHoverEvent(event);
}

param
event The hover event to dispatch to the virtual view hierarchy.
return
Whether the hover event was handled.

        if (!mManager.isEnabled() || !mManager.isTouchExplorationEnabled()) {
            return false;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_HOVER_MOVE:
            case MotionEvent.ACTION_HOVER_ENTER:
                final int virtualViewId = getVirtualViewAt(event.getX(), event.getY());
                updateHoveredVirtualView(virtualViewId);
                return (virtualViewId != INVALID_ID);
            case MotionEvent.ACTION_HOVER_EXIT:
                if (mFocusedVirtualViewId != INVALID_ID) {
                    updateHoveredVirtualView(INVALID_ID);
                    return true;
                }
                return false;
            default:
                return false;
        }
    
private voidensureTempRects()

        mTempGlobalRect = new int[2];
        mTempParentRect = new Rect();
        mTempScreenRect = new Rect();
    
public android.view.accessibility.AccessibilityNodeProvidergetAccessibilityNodeProvider(android.view.View host)
Returns the {@link android.view.accessibility.AccessibilityNodeProvider} for this helper.

param
host View whose logical children are exposed by this helper.
return
The accessibility node provider for this helper.

        if (mNodeProvider == null) {
            mNodeProvider = new ExploreByTouchNodeProvider();
        }
        return mNodeProvider;
    
public intgetFocusedVirtualView()
Returns the virtual view id for the currently focused item,

return
A virtual view id, or {@link #INVALID_ID} if no item is currently focused.

        return mFocusedVirtualViewId;
    
protected abstract intgetVirtualViewAt(float x, float y)
Provides a mapping between view-relative coordinates and logical items.

param
x The view-relative x coordinate
param
y The view-relative y coordinate
return
virtual view identifier for the logical item under coordinates (x,y)

protected abstract voidgetVisibleVirtualViews(android.util.IntArray virtualViewIds)
Populates a list with the view's visible items. The ordering of items within {@code virtualViewIds} specifies order of accessibility focus traversal.

param
virtualViewIds The list to populate with visible items

private booleanintersectVisibleToUser(android.graphics.Rect localRect)
Computes whether the specified {@link Rect} intersects with the visible portion of its parent {@link View}. Modifies {@code localRect} to contain only the visible portion.

param
localRect A rectangle in local (parent) coordinates.
return
Whether the specified {@link Rect} is visible on the screen.

        // Missing or empty bounds mean this view is not visible.
        if ((localRect == null) || localRect.isEmpty()) {
            return false;
        }

        // Attached to invisible window means this view is not visible.
        if (mView.getWindowVisibility() != View.VISIBLE) {
            return false;
        }

        // An invisible predecessor means that this view is not visible.
        ViewParent viewParent = mView.getParent();
        while (viewParent instanceof View) {
            final View view = (View) viewParent;
            if ((view.getAlpha() <= 0) || (view.getVisibility() != View.VISIBLE)) {
                return false;
            }
            viewParent = view.getParent();
        }

        // A null parent implies the view is not visible.
        if (viewParent == null) {
            return false;
        }

        // If no portion of the parent is visible, this view is not visible.
        if (mTempVisibleRect == null) {
            mTempVisibleRect = new Rect();
        }
        final Rect tempVisibleRect = mTempVisibleRect;
        if (!mView.getLocalVisibleRect(tempVisibleRect)) {
            return false;
        }

        // Check if the view intersects the visible portion of the parent.
        return localRect.intersect(tempVisibleRect);
    
public voidinvalidateRoot()
Notifies the accessibility framework that the properties of the parent view have changed.

You must call this method after adding or removing items from the parent view.

        invalidateVirtualView(View.NO_ID);
    
public voidinvalidateVirtualView(int virtualViewId)
Notifies the accessibility framework that the properties of a particular item have changed.

You must call this method after changing any of the properties set in {@link #onPopulateNodeForVirtualView}.

param
virtualViewId The virtual view id to invalidate.

        sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
    
private booleanisAccessibilityFocused(int virtualViewId)
Returns whether this virtual view is accessibility focused.

return
True if the view is accessibility focused.

        return (mFocusedVirtualViewId == virtualViewId);
    
private booleanmanageFocusForChild(int virtualViewId, int action)

        switch (action) {
            case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
                return requestAccessibilityFocus(virtualViewId);
            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
                return clearAccessibilityFocus(virtualViewId);
            default:
                return false;
        }
    
protected abstract booleanonPerformActionForVirtualView(int virtualViewId, int action, android.os.Bundle arguments)
Performs the specified accessibility action on the item associated with the virtual view identifier. See {@link AccessibilityNodeInfo#performAction(int, Bundle)} for more information.

Implementations must handle any actions added manually in {@link #onPopulateNodeForVirtualView}.

The helper class automatically handles focus management resulting from {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS} and {@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS} actions.

param
virtualViewId The virtual view identifier of the item on which to perform the action
param
action The accessibility action to perform
param
arguments (Optional) A bundle with additional arguments, or null
return
true if the action was performed

protected abstract voidonPopulateEventForVirtualView(int virtualViewId, android.view.accessibility.AccessibilityEvent event)
Populates an {@link AccessibilityEvent} with information about the specified item.

Implementations must populate the following required fields:

  • event text, see {@link AccessibilityEvent#getText} or {@link AccessibilityEvent#setContentDescription}

The helper class automatically populates the following fields with default values, but implementations may optionally override them:

  • item class name, set to android.view.View, see {@link AccessibilityEvent#setClassName}

The following required fields are automatically populated by the helper class and may not be overridden:

  • package name, set to the package of the host view's {@link Context}, see {@link AccessibilityEvent#setPackageName}
  • event source, set to the host view and virtual view identifier, see {@link AccessibilityRecord#setSource(View, int)}

param
virtualViewId The virtual view id for the item for which to populate the event
param
event The event to populate

protected abstract voidonPopulateNodeForVirtualView(int virtualViewId, android.view.accessibility.AccessibilityNodeInfo node)
Populates an {@link AccessibilityNodeInfo} with information about the specified item.

Implementations must populate the following required fields:

  • event text, see {@link AccessibilityNodeInfo#setText} or {@link AccessibilityNodeInfo#setContentDescription}
  • bounds in parent coordinates, see {@link AccessibilityNodeInfo#setBoundsInParent}

The helper class automatically populates the following fields with default values, but implementations may optionally override them:

  • enabled state, set to true, see {@link AccessibilityNodeInfo#setEnabled}
  • item class name, identical to the class name set by {@link #onPopulateEventForVirtualView}, see {@link AccessibilityNodeInfo#setClassName}

The following required fields are automatically populated by the helper class and may not be overridden:

  • package name, identical to the package name set by {@link #onPopulateEventForVirtualView}, see {@link AccessibilityNodeInfo#setPackageName}
  • node source, identical to the event source set in {@link #onPopulateEventForVirtualView}, see {@link AccessibilityNodeInfo#setSource(View, int)}
  • parent view, set to the host view, see {@link AccessibilityNodeInfo#setParent(View)}
  • visibility, computed based on parent-relative bounds, see {@link AccessibilityNodeInfo#setVisibleToUser}
  • accessibility focus, computed based on internal helper state, see {@link AccessibilityNodeInfo#setAccessibilityFocused}
  • bounds in screen coordinates, computed based on host view bounds, see {@link AccessibilityNodeInfo#setBoundsInScreen}

Additionally, the helper class automatically handles accessibility focus management by adding the appropriate {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS} or {@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS} action. Implementations must never manually add these actions.

The helper class also automatically modifies parent- and screen-relative bounds to reflect the portion of the item visible within its parent.

param
virtualViewId The virtual view identifier of the item for which to populate the node
param
node The node to populate

private booleanperformAction(int virtualViewId, int action, android.os.Bundle arguments)

        switch (virtualViewId) {
            case View.NO_ID:
                return performActionForHost(action, arguments);
            default:
                return performActionForChild(virtualViewId, action, arguments);
        }
    
private booleanperformActionForChild(int virtualViewId, int action, android.os.Bundle arguments)

        switch (action) {
            case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
                return manageFocusForChild(virtualViewId, action);
            default:
                return onPerformActionForVirtualView(virtualViewId, action, arguments);
        }
    
private booleanperformActionForHost(int action, android.os.Bundle arguments)

        return performAccessibilityAction(mView, action, arguments);
    
private booleanrequestAccessibilityFocus(int virtualViewId)
Attempts to give accessibility focus to a virtual view.

A virtual view will not actually take focus if {@link AccessibilityManager#isEnabled()} returns false, {@link AccessibilityManager#isTouchExplorationEnabled()} returns false, or the view already has accessibility focus.

param
virtualViewId The id of the virtual view on which to place accessibility focus.
return
Whether this virtual view actually took accessibility focus.

        final AccessibilityManager accessibilityManager =
                (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);

        if (!mManager.isEnabled()
                || !accessibilityManager.isTouchExplorationEnabled()) {
            return false;
        }
        // TODO: Check virtual view visibility.
        if (!isAccessibilityFocused(virtualViewId)) {
            mFocusedVirtualViewId = virtualViewId;
            // TODO: Only invalidate virtual view bounds.
            mView.invalidate();
            sendEventForVirtualView(virtualViewId,
                    AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
            return true;
        }
        return false;
    
public booleansendEventForVirtualView(int virtualViewId, int eventType)
Populates an event of the specified type with information about an item and attempts to send it up through the view hierarchy.

You should call this method after performing a user action that normally fires an accessibility event, such as clicking on an item.

public void performItemClick(T item) {
...
sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED);
}

param
virtualViewId The virtual view id for which to send an event.
param
eventType The type of event to send.
return
true if the event was sent successfully.

        if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) {
            return false;
        }

        final ViewParent parent = mView.getParent();
        if (parent == null) {
            return false;
        }

        final AccessibilityEvent event = createEvent(virtualViewId, eventType);
        return parent.requestSendAccessibilityEvent(mView, event);
    
private voidupdateHoveredVirtualView(int virtualViewId)
Sets the currently hovered item, sending hover accessibility events as necessary to maintain the correct state.

param
virtualViewId The virtual view id for the item currently being hovered, or {@link #INVALID_ID} if no item is hovered within the parent view.

        if (mHoveredVirtualViewId == virtualViewId) {
            return;
        }

        final int previousVirtualViewId = mHoveredVirtualViewId;
        mHoveredVirtualViewId = virtualViewId;

        // Stay consistent with framework behavior by sending ENTER/EXIT pairs
        // in reverse order. This is accurate as of API 18.
        sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
        sendEventForVirtualView(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);