Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.widget;
     18 
     19 import android.content.Context;
     20 import android.graphics.Rect;
     21 import android.os.Bundle;
     22 import android.view.accessibility.*;
     23 import android.view.MotionEvent;
     24 import android.view.View;
     25 import android.view.ViewParent;
     26 import android.view.accessibility.AccessibilityEvent;
     27 import android.view.accessibility.AccessibilityManager;
     28 import android.view.accessibility.AccessibilityNodeInfo;
     29 import android.view.accessibility.AccessibilityNodeProvider;
     30 
     31 import java.util.LinkedList;
     32 import java.util.List;
     33 
     34 /**
     35  * ExploreByTouchHelper is a utility class for implementing accessibility
     36  * support in custom {@link android.view.View}s that represent a collection of View-like
     37  * logical items. It extends {@link android.view.accessibility.AccessibilityNodeProvider} and
     38  * simplifies many aspects of providing information to accessibility services
     39  * and managing accessibility focus. This class does not currently support
     40  * hierarchies of logical items.
     41  * <p>
     42  * This should be applied to the parent view using
     43  * {@link android.view.View#setAccessibilityDelegate}:
     44  *
     45  * <pre>
     46  * mAccessHelper = ExploreByTouchHelper.create(someView, mAccessHelperCallback);
     47  * ViewCompat.setAccessibilityDelegate(someView, mAccessHelper);
     48  * </pre>
     49  */
     50 public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
     51     /** Virtual node identifier value for invalid nodes. */
     52     public static final int INVALID_ID = Integer.MIN_VALUE;
     53 
     54     /** Default class name used for virtual views. */
     55     private static final String DEFAULT_CLASS_NAME = View.class.getName();
     56 
     57     // Temporary, reusable data structures.
     58     private final Rect mTempScreenRect = new Rect();
     59     private final Rect mTempParentRect = new Rect();
     60     private final Rect mTempVisibleRect = new Rect();
     61     private final int[] mTempGlobalRect = new int[2];
     62 
     63     /** View's context **/
     64     private Context mContext;
     65 
     66     /** System accessibility manager, used to check state and send events. */
     67     private final AccessibilityManager mManager;
     68 
     69     /** View whose internal structure is exposed through this helper. */
     70     private final View mView;
     71 
     72     /** Node provider that handles creating nodes and performing actions. */
     73     private ExploreByTouchNodeProvider mNodeProvider;
     74 
     75     /** Virtual view id for the currently focused logical item. */
     76     private int mFocusedVirtualViewId = INVALID_ID;
     77 
     78     /** Virtual view id for the currently hovered logical item. */
     79     private int mHoveredVirtualViewId = INVALID_ID;
     80 
     81     /**
     82      * Factory method to create a new {@link ExploreByTouchHelper}.
     83      *
     84      * @param forView View whose logical children are exposed by this helper.
     85      */
     86     public ExploreByTouchHelper(View forView) {
     87         if (forView == null) {
     88             throw new IllegalArgumentException("View may not be null");
     89         }
     90 
     91         mView = forView;
     92         mContext = forView.getContext();
     93         mManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
     94     }
     95 
     96     /**
     97      * Returns the {@link android.view.accessibility.AccessibilityNodeProvider} for this helper.
     98      *
     99      * @param host View whose logical children are exposed by this helper.
    100      * @return The accessibility node provider for this helper.
    101      */
    102     @Override
    103     public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
    104         if (mNodeProvider == null) {
    105             mNodeProvider = new ExploreByTouchNodeProvider();
    106         }
    107         return mNodeProvider;
    108     }
    109 
    110     /**
    111      * Dispatches hover {@link android.view.MotionEvent}s to the virtual view hierarchy when
    112      * the Explore by Touch feature is enabled.
    113      * <p>
    114      * This method should be called by overriding
    115      * {@link View#dispatchHoverEvent}:
    116      *
    117      * <pre>&#64;Override
    118      * public boolean dispatchHoverEvent(MotionEvent event) {
    119      *   if (mHelper.dispatchHoverEvent(this, event) {
    120      *     return true;
    121      *   }
    122      *   return super.dispatchHoverEvent(event);
    123      * }
    124      * </pre>
    125      *
    126      * @param event The hover event to dispatch to the virtual view hierarchy.
    127      * @return Whether the hover event was handled.
    128      */
    129     public boolean dispatchHoverEvent(MotionEvent event) {
    130         if (!mManager.isEnabled() || !mManager.isTouchExplorationEnabled()) {
    131             return false;
    132         }
    133 
    134         switch (event.getAction()) {
    135             case MotionEvent.ACTION_HOVER_MOVE:
    136             case MotionEvent.ACTION_HOVER_ENTER:
    137                 final int virtualViewId = getVirtualViewAt(event.getX(), event.getY());
    138                 updateHoveredVirtualView(virtualViewId);
    139                 return (virtualViewId != INVALID_ID);
    140             case MotionEvent.ACTION_HOVER_EXIT:
    141                 if (mFocusedVirtualViewId != INVALID_ID) {
    142                     updateHoveredVirtualView(INVALID_ID);
    143                     return true;
    144                 }
    145                 return false;
    146             default:
    147                 return false;
    148         }
    149     }
    150 
    151     /**
    152      * Populates an event of the specified type with information about an item
    153      * and attempts to send it up through the view hierarchy.
    154      * <p>
    155      * You should call this method after performing a user action that normally
    156      * fires an accessibility event, such as clicking on an item.
    157      *
    158      * <pre>public void performItemClick(T item) {
    159      *   ...
    160      *   sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED);
    161      * }
    162      * </pre>
    163      *
    164      * @param virtualViewId The virtual view id for which to send an event.
    165      * @param eventType The type of event to send.
    166      * @return true if the event was sent successfully.
    167      */
    168     public boolean sendEventForVirtualView(int virtualViewId, int eventType) {
    169         if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) {
    170             return false;
    171         }
    172 
    173         final ViewParent parent = mView.getParent();
    174         if (parent == null) {
    175             return false;
    176         }
    177 
    178         final AccessibilityEvent event = createEvent(virtualViewId, eventType);
    179         return parent.requestSendAccessibilityEvent(mView, event);
    180     }
    181 
    182     /**
    183      * Notifies the accessibility framework that the properties of the parent
    184      * view have changed.
    185      * <p>
    186      * You <b>must</b> call this method after adding or removing items from the
    187      * parent view.
    188      */
    189     public void invalidateRoot() {
    190         invalidateVirtualView(View.NO_ID);
    191     }
    192 
    193     /**
    194      * Notifies the accessibility framework that the properties of a particular
    195      * item have changed.
    196      * <p>
    197      * You <b>must</b> call this method after changing any of the properties set
    198      * in {@link #onPopulateNodeForVirtualView}.
    199      *
    200      * @param virtualViewId The virtual view id to invalidate.
    201      */
    202     public void invalidateVirtualView(int virtualViewId) {
    203         sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
    204     }
    205 
    206     /**
    207      * Returns the virtual view id for the currently focused item,
    208      *
    209      * @return A virtual view id, or {@link #INVALID_ID} if no item is
    210      *         currently focused.
    211      */
    212     public int getFocusedVirtualView() {
    213         return mFocusedVirtualViewId;
    214     }
    215 
    216     /**
    217      * Sets the currently hovered item, sending hover accessibility events as
    218      * necessary to maintain the correct state.
    219      *
    220      * @param virtualViewId The virtual view id for the item currently being
    221      *            hovered, or {@link #INVALID_ID} if no item is hovered within
    222      *            the parent view.
    223      */
    224     private void updateHoveredVirtualView(int virtualViewId) {
    225         if (mHoveredVirtualViewId == virtualViewId) {
    226             return;
    227         }
    228 
    229         final int previousVirtualViewId = mHoveredVirtualViewId;
    230         mHoveredVirtualViewId = virtualViewId;
    231 
    232         // Stay consistent with framework behavior by sending ENTER/EXIT pairs
    233         // in reverse order. This is accurate as of API 18.
    234         sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
    235         sendEventForVirtualView(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
    236     }
    237 
    238     /**
    239      * Constructs and returns an {@link AccessibilityEvent} for the specified
    240      * virtual view id, which includes the host view ({@link View#NO_ID}).
    241      *
    242      * @param virtualViewId The virtual view id for the item for which to
    243      *            construct an event.
    244      * @param eventType The type of event to construct.
    245      * @return An {@link AccessibilityEvent} populated with information about
    246      *         the specified item.
    247      */
    248     private AccessibilityEvent createEvent(int virtualViewId, int eventType) {
    249         switch (virtualViewId) {
    250             case View.NO_ID:
    251                 return createEventForHost(eventType);
    252             default:
    253                 return createEventForChild(virtualViewId, eventType);
    254         }
    255     }
    256 
    257     /**
    258      * Constructs and returns an {@link AccessibilityEvent} for the host node.
    259      *
    260      * @param eventType The type of event to construct.
    261      * @return An {@link AccessibilityEvent} populated with information about
    262      *         the specified item.
    263      */
    264     private AccessibilityEvent createEventForHost(int eventType) {
    265         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
    266         onInitializeAccessibilityEvent(mView, event);
    267         return event;
    268     }
    269 
    270     /**
    271      * Constructs and returns an {@link AccessibilityEvent} populated with
    272      * information about the specified item.
    273      *
    274      * @param virtualViewId The virtual view id for the item for which to
    275      *            construct an event.
    276      * @param eventType The type of event to construct.
    277      * @return An {@link AccessibilityEvent} populated with information about
    278      *         the specified item.
    279      */
    280     private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
    281         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
    282         event.setEnabled(true);
    283         event.setClassName(DEFAULT_CLASS_NAME);
    284 
    285         // Allow the client to populate the event.
    286         onPopulateEventForVirtualView(virtualViewId, event);
    287 
    288         // Make sure the developer is following the rules.
    289         if (event.getText().isEmpty() && (event.getContentDescription() == null)) {
    290             throw new RuntimeException("Callbacks must add text or a content description in "
    291                     + "populateEventForVirtualViewId()");
    292         }
    293 
    294         // Don't allow the client to override these properties.
    295         event.setPackageName(mView.getContext().getPackageName());
    296         event.setSource(mView, virtualViewId);
    297 
    298         return event;
    299     }
    300 
    301     /**
    302      * Constructs and returns an {@link android.view.accessibility.AccessibilityNodeInfo} for the
    303      * specified virtual view id, which includes the host view
    304      * ({@link View#NO_ID}).
    305      *
    306      * @param virtualViewId The virtual view id for the item for which to
    307      *            construct a node.
    308      * @return An {@link android.view.accessibility.AccessibilityNodeInfo} populated with information
    309      *         about the specified item.
    310      */
    311     private AccessibilityNodeInfo createNode(int virtualViewId) {
    312         switch (virtualViewId) {
    313             case View.NO_ID:
    314                 return createNodeForHost();
    315             default:
    316                 return createNodeForChild(virtualViewId);
    317         }
    318     }
    319 
    320     /**
    321      * Constructs and returns an {@link AccessibilityNodeInfo} for the
    322      * host view populated with its virtual descendants.
    323      *
    324      * @return An {@link AccessibilityNodeInfo} for the parent node.
    325      */
    326     private AccessibilityNodeInfo createNodeForHost() {
    327         final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView);
    328         onInitializeAccessibilityNodeInfo(mView, node);
    329 
    330         // Add the virtual descendants.
    331         final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>();
    332         getVisibleVirtualViews(virtualViewIds);
    333 
    334         for (Integer childVirtualViewId : virtualViewIds) {
    335             node.addChild(mView, childVirtualViewId);
    336         }
    337 
    338         return node;
    339     }
    340 
    341     /**
    342      * Constructs and returns an {@link AccessibilityNodeInfo} for the
    343      * specified item. Automatically manages accessibility focus actions.
    344      * <p>
    345      * Allows the implementing class to specify most node properties, but
    346      * overrides the following:
    347      * <ul>
    348      * <li>{@link AccessibilityNodeInfo#setPackageName}
    349      * <li>{@link AccessibilityNodeInfo#setClassName}
    350      * <li>{@link AccessibilityNodeInfo#setParent(View)}
    351      * <li>{@link AccessibilityNodeInfo#setSource(View, int)}
    352      * <li>{@link AccessibilityNodeInfo#setVisibleToUser}
    353      * <li>{@link AccessibilityNodeInfo#setBoundsInScreen(Rect)}
    354      * </ul>
    355      * <p>
    356      * Uses the bounds of the parent view and the parent-relative bounding
    357      * rectangle specified by
    358      * {@link AccessibilityNodeInfo#getBoundsInParent} to automatically
    359      * update the following properties:
    360      * <ul>
    361      * <li>{@link AccessibilityNodeInfo#setVisibleToUser}
    362      * <li>{@link AccessibilityNodeInfo#setBoundsInParent}
    363      * </ul>
    364      *
    365      * @param virtualViewId The virtual view id for item for which to construct
    366      *            a node.
    367      * @return An {@link AccessibilityNodeInfo} for the specified item.
    368      */
    369     private AccessibilityNodeInfo createNodeForChild(int virtualViewId) {
    370         final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
    371 
    372         // Ensure the client has good defaults.
    373         node.setEnabled(true);
    374         node.setClassName(DEFAULT_CLASS_NAME);
    375 
    376         // Allow the client to populate the node.
    377         onPopulateNodeForVirtualView(virtualViewId, node);
    378 
    379         // Make sure the developer is following the rules.
    380         if ((node.getText() == null) && (node.getContentDescription() == null)) {
    381             throw new RuntimeException("Callbacks must add text or a content description in "
    382                     + "populateNodeForVirtualViewId()");
    383         }
    384 
    385         node.getBoundsInParent(mTempParentRect);
    386         if (mTempParentRect.isEmpty()) {
    387             throw new RuntimeException("Callbacks must set parent bounds in "
    388                     + "populateNodeForVirtualViewId()");
    389         }
    390 
    391         final int actions = node.getActions();
    392         if ((actions & AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) != 0) {
    393             throw new RuntimeException("Callbacks must not add ACTION_ACCESSIBILITY_FOCUS in "
    394                     + "populateNodeForVirtualViewId()");
    395         }
    396         if ((actions & AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) != 0) {
    397             throw new RuntimeException("Callbacks must not add ACTION_CLEAR_ACCESSIBILITY_FOCUS in "
    398                     + "populateNodeForVirtualViewId()");
    399         }
    400 
    401         // Don't allow the client to override these properties.
    402         node.setPackageName(mView.getContext().getPackageName());
    403         node.setSource(mView, virtualViewId);
    404         node.setParent(mView);
    405 
    406         // Manage internal accessibility focus state.
    407         if (mFocusedVirtualViewId == virtualViewId) {
    408             node.setAccessibilityFocused(true);
    409             node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
    410         } else {
    411             node.setAccessibilityFocused(false);
    412             node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
    413         }
    414 
    415         // Set the visibility based on the parent bound.
    416         if (intersectVisibleToUser(mTempParentRect)) {
    417             node.setVisibleToUser(true);
    418             node.setBoundsInParent(mTempParentRect);
    419         }
    420 
    421         // Calculate screen-relative bound.
    422         mView.getLocationOnScreen(mTempGlobalRect);
    423         final int offsetX = mTempGlobalRect[0];
    424         final int offsetY = mTempGlobalRect[1];
    425         mTempScreenRect.set(mTempParentRect);
    426         mTempScreenRect.offset(offsetX, offsetY);
    427         node.setBoundsInScreen(mTempScreenRect);
    428 
    429         return node;
    430     }
    431 
    432     private boolean performAction(int virtualViewId, int action, Bundle arguments) {
    433         switch (virtualViewId) {
    434             case View.NO_ID:
    435                 return performActionForHost(action, arguments);
    436             default:
    437                 return performActionForChild(virtualViewId, action, arguments);
    438         }
    439     }
    440 
    441     private boolean performActionForHost(int action, Bundle arguments) {
    442         return performAccessibilityAction(mView, action, arguments);
    443     }
    444 
    445     private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) {
    446         switch (action) {
    447             case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
    448             case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
    449                 return manageFocusForChild(virtualViewId, action, arguments);
    450             default:
    451                 return onPerformActionForVirtualView(virtualViewId, action, arguments);
    452         }
    453     }
    454 
    455     private boolean manageFocusForChild(int virtualViewId, int action, Bundle arguments) {
    456         switch (action) {
    457             case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
    458                 return requestAccessibilityFocus(virtualViewId);
    459             case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
    460                 return clearAccessibilityFocus(virtualViewId);
    461             default:
    462                 return false;
    463         }
    464     }
    465 
    466     /**
    467      * Computes whether the specified {@link Rect} intersects with the visible
    468      * portion of its parent {@link View}. Modifies {@code localRect} to contain
    469      * only the visible portion.
    470      *
    471      * @param localRect A rectangle in local (parent) coordinates.
    472      * @return Whether the specified {@link Rect} is visible on the screen.
    473      */
    474     private boolean intersectVisibleToUser(Rect localRect) {
    475         // Missing or empty bounds mean this view is not visible.
    476         if ((localRect == null) || localRect.isEmpty()) {
    477             return false;
    478         }
    479 
    480         // Attached to invisible window means this view is not visible.
    481         if (mView.getWindowVisibility() != View.VISIBLE) {
    482             return false;
    483         }
    484 
    485         // An invisible predecessor means that this view is not visible.
    486         ViewParent viewParent = mView.getParent();
    487         while (viewParent instanceof View) {
    488             final View view = (View) viewParent;
    489             if ((view.getAlpha() <= 0) || (view.getVisibility() != View.VISIBLE)) {
    490                 return false;
    491             }
    492             viewParent = view.getParent();
    493         }
    494 
    495         // A null parent implies the view is not visible.
    496         if (viewParent == null) {
    497             return false;
    498         }
    499 
    500         // If no portion of the parent is visible, this view is not visible.
    501         if (!mView.getLocalVisibleRect(mTempVisibleRect)) {
    502             return false;
    503         }
    504 
    505         // Check if the view intersects the visible portion of the parent.
    506         return localRect.intersect(mTempVisibleRect);
    507     }
    508 
    509     /**
    510      * Returns whether this virtual view is accessibility focused.
    511      *
    512      * @return True if the view is accessibility focused.
    513      */
    514     private boolean isAccessibilityFocused(int virtualViewId) {
    515         return (mFocusedVirtualViewId == virtualViewId);
    516     }
    517 
    518     /**
    519      * Attempts to give accessibility focus to a virtual view.
    520      * <p>
    521      * A virtual view will not actually take focus if
    522      * {@link AccessibilityManager#isEnabled()} returns false,
    523      * {@link AccessibilityManager#isTouchExplorationEnabled()} returns false,
    524      * or the view already has accessibility focus.
    525      *
    526      * @param virtualViewId The id of the virtual view on which to place
    527      *            accessibility focus.
    528      * @return Whether this virtual view actually took accessibility focus.
    529      */
    530     private boolean requestAccessibilityFocus(int virtualViewId) {
    531         final AccessibilityManager accessibilityManager =
    532                 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
    533 
    534         if (!mManager.isEnabled()
    535                 || !accessibilityManager.isTouchExplorationEnabled()) {
    536             return false;
    537         }
    538         // TODO: Check virtual view visibility.
    539         if (!isAccessibilityFocused(virtualViewId)) {
    540             mFocusedVirtualViewId = virtualViewId;
    541             // TODO: Only invalidate virtual view bounds.
    542             mView.invalidate();
    543             sendEventForVirtualView(virtualViewId,
    544                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
    545             return true;
    546         }
    547         return false;
    548     }
    549 
    550     /**
    551      * Attempts to clear accessibility focus from a virtual view.
    552      *
    553      * @param virtualViewId The id of the virtual view from which to clear
    554      *            accessibility focus.
    555      * @return Whether this virtual view actually cleared accessibility focus.
    556      */
    557     private boolean clearAccessibilityFocus(int virtualViewId) {
    558         if (isAccessibilityFocused(virtualViewId)) {
    559             mFocusedVirtualViewId = INVALID_ID;
    560             mView.invalidate();
    561             sendEventForVirtualView(virtualViewId,
    562                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
    563             return true;
    564         }
    565         return false;
    566     }
    567 
    568     /**
    569      * Provides a mapping between view-relative coordinates and logical
    570      * items.
    571      *
    572      * @param x The view-relative x coordinate
    573      * @param y The view-relative y coordinate
    574      * @return virtual view identifier for the logical item under
    575      *         coordinates (x,y)
    576      */
    577     protected abstract int getVirtualViewAt(float x, float y);
    578 
    579     /**
    580      * Populates a list with the view's visible items. The ordering of items
    581      * within {@code virtualViewIds} specifies order of accessibility focus
    582      * traversal.
    583      *
    584      * @param virtualViewIds The list to populate with visible items
    585      */
    586     protected abstract void getVisibleVirtualViews(List<Integer> virtualViewIds);
    587 
    588     /**
    589      * Populates an {@link AccessibilityEvent} with information about the
    590      * specified item.
    591      * <p>
    592      * Implementations <b>must</b> populate the following required fields:
    593      * <ul>
    594      * <li>event text, see {@link AccessibilityEvent#getText} or
    595      * {@link AccessibilityEvent#setContentDescription}
    596      * </ul>
    597      * <p>
    598      * The helper class automatically populates the following fields with
    599      * default values, but implementations may optionally override them:
    600      * <ul>
    601      * <li>item class name, set to android.view.View, see
    602      * {@link AccessibilityEvent#setClassName}
    603      * </ul>
    604      * <p>
    605      * The following required fields are automatically populated by the
    606      * helper class and may not be overridden:
    607      * <ul>
    608      * <li>package name, set to the package of the host view's
    609      * {@link Context}, see {@link AccessibilityEvent#setPackageName}
    610      * <li>event source, set to the host view and virtual view identifier,
    611      * see {@link AccessibilityRecord#setSource(View, int)}
    612      * </ul>
    613      *
    614      * @param virtualViewId The virtual view id for the item for which to
    615      *            populate the event
    616      * @param event The event to populate
    617      */
    618     protected abstract void onPopulateEventForVirtualView(
    619             int virtualViewId, AccessibilityEvent event);
    620 
    621     /**
    622      * Populates an {@link AccessibilityNodeInfo} with information
    623      * about the specified item.
    624      * <p>
    625      * Implementations <b>must</b> populate the following required fields:
    626      * <ul>
    627      * <li>event text, see {@link AccessibilityNodeInfo#setText} or
    628      * {@link AccessibilityNodeInfo#setContentDescription}
    629      * <li>bounds in parent coordinates, see
    630      * {@link AccessibilityNodeInfo#setBoundsInParent}
    631      * </ul>
    632      * <p>
    633      * The helper class automatically populates the following fields with
    634      * default values, but implementations may optionally override them:
    635      * <ul>
    636      * <li>enabled state, set to true, see
    637      * {@link AccessibilityNodeInfo#setEnabled}
    638      * <li>item class name, identical to the class name set by
    639      * {@link #onPopulateEventForVirtualView}, see
    640      * {@link AccessibilityNodeInfo#setClassName}
    641      * </ul>
    642      * <p>
    643      * The following required fields are automatically populated by the
    644      * helper class and may not be overridden:
    645      * <ul>
    646      * <li>package name, identical to the package name set by
    647      * {@link #onPopulateEventForVirtualView}, see
    648      * {@link AccessibilityNodeInfo#setPackageName}
    649      * <li>node source, identical to the event source set in
    650      * {@link #onPopulateEventForVirtualView}, see
    651      * {@link AccessibilityNodeInfo#setSource(View, int)}
    652      * <li>parent view, set to the host view, see
    653      * {@link AccessibilityNodeInfo#setParent(View)}
    654      * <li>visibility, computed based on parent-relative bounds, see
    655      * {@link AccessibilityNodeInfo#setVisibleToUser}
    656      * <li>accessibility focus, computed based on internal helper state, see
    657      * {@link AccessibilityNodeInfo#setAccessibilityFocused}
    658      * <li>bounds in screen coordinates, computed based on host view bounds,
    659      * see {@link AccessibilityNodeInfo#setBoundsInScreen}
    660      * </ul>
    661      * <p>
    662      * Additionally, the helper class automatically handles accessibility
    663      * focus management by adding the appropriate
    664      * {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS} or
    665      * {@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
    666      * action. Implementations must <b>never</b> manually add these actions.
    667      * <p>
    668      * The helper class also automatically modifies parent- and
    669      * screen-relative bounds to reflect the portion of the item visible
    670      * within its parent.
    671      *
    672      * @param virtualViewId The virtual view identifier of the item for
    673      *            which to populate the node
    674      * @param node The node to populate
    675      */
    676     protected abstract void onPopulateNodeForVirtualView(
    677             int virtualViewId, AccessibilityNodeInfo node);
    678 
    679     /**
    680      * Performs the specified accessibility action on the item associated
    681      * with the virtual view identifier. See
    682      * {@link AccessibilityNodeInfo#performAction(int, Bundle)} for
    683      * more information.
    684      * <p>
    685      * Implementations <b>must</b> handle any actions added manually in
    686      * {@link #onPopulateNodeForVirtualView}.
    687      * <p>
    688      * The helper class automatically handles focus management resulting
    689      * from {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS}
    690      * and
    691      * {@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
    692      * actions.
    693      *
    694      * @param virtualViewId The virtual view identifier of the item on which
    695      *            to perform the action
    696      * @param action The accessibility action to perform
    697      * @param arguments (Optional) A bundle with additional arguments, or
    698      *            null
    699      * @return true if the action was performed
    700      */
    701     protected abstract boolean onPerformActionForVirtualView(
    702             int virtualViewId, int action, Bundle arguments);
    703 
    704     /**
    705      * Exposes a virtual view hierarchy to the accessibility framework. Only
    706      * used in API 16+.
    707      */
    708     private class ExploreByTouchNodeProvider extends AccessibilityNodeProvider {
    709         @Override
    710         public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
    711             return ExploreByTouchHelper.this.createNode(virtualViewId);
    712         }
    713 
    714         @Override
    715         public boolean performAction(int virtualViewId, int action, Bundle arguments) {
    716             return ExploreByTouchHelper.this.performAction(virtualViewId, action, arguments);
    717         }
    718     }
    719 }
    720