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