FileDocCategorySizeDatePackage
ExploreByTouchHelper.javaAPI DocAndroid 5.1 API29102Thu Mar 12 22:22:56 GMT 2015android.support.v4.widget

ExploreByTouchHelper

public abstract class ExploreByTouchHelper extends android.support.v4.view.AccessibilityDelegateCompat
ExploreByTouchHelper is a utility class for implementing accessibility support in custom {@link View}s that represent a collection of View-like logical items. It extends {@link AccessibilityNodeProviderCompat} 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 ViewCompat#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 final android.graphics.Rect
mTempScreenRect
private final android.graphics.Rect
mTempParentRect
private final android.graphics.Rect
mTempVisibleRect
private final int[]
mTempGlobalRect
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 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;
        final Context context = forView.getContext();
        mManager = (AccessibilityManager) context.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,
                    AccessibilityEventCompat.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());

        final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
        record.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);
        ViewCompat.onInitializeAccessibilityEvent(mView, event);
        return event;
    
private android.support.v4.view.accessibility.AccessibilityNodeInfoCompatcreateNode(int virtualViewId)
Constructs and returns an {@link AccessibilityNodeInfoCompat} 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 AccessibilityNodeInfoCompat} populated with information about the specified item.

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

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

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

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

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

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

        final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain();

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

        // 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(mTempParentRect);
        if (mTempParentRect.isEmpty()) {
            throw new RuntimeException("Callbacks must set parent bounds in "
                    + "populateNodeForVirtualViewId()");
        }

        final int actions = node.getActions();
        if ((actions & AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS) != 0) {
            throw new RuntimeException("Callbacks must not add ACTION_ACCESSIBILITY_FOCUS in "
                    + "populateNodeForVirtualViewId()");
        }
        if ((actions & AccessibilityNodeInfoCompat.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(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
        } else {
            node.setAccessibilityFocused(false);
            node.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
        }

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

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

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

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

        final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain(mView);
        ViewCompat.onInitializeAccessibilityNodeInfo(mView, node);

        // Add the virtual descendants.
        final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>();
        getVisibleVirtualViews(virtualViewIds);

        for (Integer childVirtualViewId : virtualViewIds) {
            node.addChild(mView, childVirtualViewId);
        }

        return node;
    
public booleandispatchHoverEvent(android.view.MotionEvent event)
Dispatches hover {@link 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()
                || !AccessibilityManagerCompat.isTouchExplorationEnabled(mManager)) {
            return false;
        }

        switch (event.getAction()) {
            case MotionEventCompat.ACTION_HOVER_MOVE:
            case MotionEventCompat.ACTION_HOVER_ENTER:
                final int virtualViewId = getVirtualViewAt(event.getX(), event.getY());
                updateHoveredVirtualView(virtualViewId);
                return (virtualViewId != INVALID_ID);
            case MotionEventCompat.ACTION_HOVER_EXIT:
                if (mFocusedVirtualViewId != INVALID_ID) {
                    updateHoveredVirtualView(INVALID_ID);
                    return true;
                }
                return false;
            default:
                return false;
        }
    
public android.support.v4.view.accessibility.AccessibilityNodeProviderCompatgetAccessibilityNodeProvider(android.view.View host)
Returns the {@link AccessibilityNodeProviderCompat} 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) or {@link View#NO_ID} if there is no item at the given coordinates

protected abstract voidgetVisibleVirtualViews(java.util.List 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 ((ViewCompat.getAlpha(view) <= 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 (!mView.getLocalVisibleRect(mTempVisibleRect)) {
            return false;
        }

        // Check if the view intersects the visible portion of the parent.
        return localRect.intersect(mTempVisibleRect);
    
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, AccessibilityEventCompat.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, android.os.Bundle arguments)

        switch (action) {
            case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
                return requestAccessibilityFocus(virtualViewId);
            case AccessibilityNodeInfoCompat.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 AccessibilityNodeInfoCompat#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 AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS} and {@link AccessibilityNodeInfoCompat#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 AccessibilityRecordCompat#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.support.v4.view.accessibility.AccessibilityNodeInfoCompat node)
Populates an {@link AccessibilityNodeInfoCompat} with information about the specified item.

Implementations must populate the following required fields:

  • event text, see {@link AccessibilityNodeInfoCompat#setText} or {@link AccessibilityNodeInfoCompat#setContentDescription}
  • bounds in parent coordinates, see {@link AccessibilityNodeInfoCompat#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 AccessibilityNodeInfoCompat#setEnabled}
  • item class name, identical to the class name set by {@link #onPopulateEventForVirtualView}, see {@link AccessibilityNodeInfoCompat#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 AccessibilityNodeInfoCompat#setPackageName}
  • node source, identical to the event source set in {@link #onPopulateEventForVirtualView}, see {@link AccessibilityNodeInfoCompat#setSource(View, int)}
  • parent view, set to the host view, see {@link AccessibilityNodeInfoCompat#setParent(View)}
  • visibility, computed based on parent-relative bounds, see {@link AccessibilityNodeInfoCompat#setVisibleToUser}
  • accessibility focus, computed based on internal helper state, see {@link AccessibilityNodeInfoCompat#setAccessibilityFocused}
  • bounds in screen coordinates, computed based on host view bounds, see {@link AccessibilityNodeInfoCompat#setBoundsInScreen}

Additionally, the helper class automatically handles accessibility focus management by adding the appropriate {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS} or {@link AccessibilityNodeInfoCompat#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 AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
            case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
                return manageFocusForChild(virtualViewId, action, arguments);
            default:
                return onPerformActionForVirtualView(virtualViewId, action, arguments);
        }
    
private booleanperformActionForHost(int action, android.os.Bundle arguments)

        return ViewCompat.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.

        if (!mManager.isEnabled()
                || !AccessibilityManagerCompat.isTouchExplorationEnabled(mManager)) {
            return false;
        }
        // TODO: Check virtual view visibility.
        if (!isAccessibilityFocused(virtualViewId)) {
            mFocusedVirtualViewId = virtualViewId;
            // TODO: Only invalidate virtual view bounds.
            mView.invalidate();
            sendEventForVirtualView(virtualViewId,
                    AccessibilityEventCompat.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 ViewParentCompat.requestSendAccessibilityEvent(parent, 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, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
        sendEventForVirtualView(
                previousVirtualViewId, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);