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