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