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