ExploreByTouchHelper.java revision 3236f136c34c3db9b05c69cdbaccdc0fa6203a1b
16eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette/*
26eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * Copyright (C) 2013 The Android Open Source Project
36eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette *
46eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License");
56eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * you may not use this file except in compliance with the License.
66eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * You may obtain a copy of the License at
76eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette *
86eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette *      http://www.apache.org/licenses/LICENSE-2.0
96eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette *
106eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * Unless required by applicable law or agreed to in writing, software
116eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS,
126eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * See the License for the specific language governing permissions and
146eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * limitations under the License.
156eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette */
166eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
176eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverettepackage android.support.v4.widget;
186eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
196eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.content.Context;
206eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.graphics.Rect;
216eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.os.Bundle;
22049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.annotation.NonNull;
23049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.annotation.Nullable;
24e69072d0197b6360882acd76d4f0271727cfe5bcAlan Viveretteimport android.support.v4.util.SparseArrayCompat;
256eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.support.v4.view.AccessibilityDelegateCompat;
266eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.support.v4.view.ViewCompat;
277f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viveretteimport android.support.v4.view.ViewCompat.FocusDirection;
287f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viveretteimport android.support.v4.view.ViewCompat.FocusRealDirection;
296eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.support.v4.view.ViewParentCompat;
306eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.support.v4.view.accessibility.AccessibilityEventCompat;
316eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.support.v4.view.accessibility.AccessibilityManagerCompat;
326eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
336eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
346eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.support.v4.view.accessibility.AccessibilityRecordCompat;
35049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.view.KeyEvent;
366eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.view.MotionEvent;
376eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.view.View;
386eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.view.ViewParent;
396eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.view.accessibility.AccessibilityEvent;
406eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport android.view.accessibility.AccessibilityManager;
4114d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikasimport android.view.accessibility.AccessibilityRecord;
426eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
43049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport java.util.ArrayList;
446eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viveretteimport java.util.List;
456eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
466eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette/**
476eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * ExploreByTouchHelper is a utility class for implementing accessibility
486eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * support in custom {@link View}s that represent a collection of View-like
496eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * logical items. It extends {@link AccessibilityNodeProviderCompat} and
506eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * simplifies many aspects of providing information to accessibility services
5167f879143b4b6d4bb07a90437df66174791781e8Zach Kuznia * and managing accessibility focus.
526eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * <p>
5312d8b3f36c7b503221185b44791822e59e690966Alan Viverette * Clients should override abstract methods on this class and attach it to the
5412d8b3f36c7b503221185b44791822e59e690966Alan Viverette * host view using {@link ViewCompat#setAccessibilityDelegate}:
55049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * <p>
566eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * <pre>
57049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * class MyCustomView extends View {
58049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     private MyVirtualViewHelper mVirtualViewHelper;
59049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *
60049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     public MyCustomView(Context context, ...) {
61049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *         ...
62049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *         mVirtualViewHelper = new MyVirtualViewHelper(this);
63049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *         ViewCompat.setAccessibilityDelegate(this, mVirtualViewHelper);
64049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     }
65049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *
66049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     &#64;Override
67049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     public boolean dispatchHoverEvent(MotionEvent event) {
68049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *       return mHelper.dispatchHoverEvent(this, event)
69049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *           || super.dispatchHoverEvent(event);
70049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     }
71049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *
72049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     &#64;Override
73049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     public boolean dispatchKeyEvent(KeyEvent event) {
74049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *       return mHelper.dispatchKeyEvent(event)
75049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *           || super.dispatchKeyEvent(event);
76049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     }
77049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *
78049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     &#64;Override
79049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     public boolean onFocusChanged(boolean gainFocus, int direction,
80049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *         Rect previouslyFocusedRect) {
81049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *       super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
82049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *       mHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
83049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *     }
84049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * }
8512d8b3f36c7b503221185b44791822e59e690966Alan Viverette * mAccessHelper = new MyExploreByTouchHelper(someView);
866eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * ViewCompat.setAccessibilityDelegate(someView, mAccessHelper);
876eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette * </pre>
886eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette */
896eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverettepublic abstract class ExploreByTouchHelper extends AccessibilityDelegateCompat {
906eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /** Virtual node identifier value for invalid nodes. */
916eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    public static final int INVALID_ID = Integer.MIN_VALUE;
926eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
9312d8b3f36c7b503221185b44791822e59e690966Alan Viverette    /** Virtual node identifier value for the host view's node. */
9412d8b3f36c7b503221185b44791822e59e690966Alan Viverette    public static final int HOST_ID = View.NO_ID;
9512d8b3f36c7b503221185b44791822e59e690966Alan Viverette
966eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /** Default class name used for virtual views. */
97049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static final String DEFAULT_CLASS_NAME = "android.view.View";
986eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
99cc35612e283564c245408da4bb9264f38a02487aAlan Viverette    /** Default bounds used to determine if the client didn't set any. */
100cc35612e283564c245408da4bb9264f38a02487aAlan Viverette    private static final Rect INVALID_PARENT_BOUNDS = new Rect(
101cc35612e283564c245408da4bb9264f38a02487aAlan Viverette            Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
102cc35612e283564c245408da4bb9264f38a02487aAlan Viverette
1036eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    // Temporary, reusable data structures.
1046eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private final Rect mTempScreenRect = new Rect();
1056eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private final Rect mTempParentRect = new Rect();
1066eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private final Rect mTempVisibleRect = new Rect();
1076eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private final int[] mTempGlobalRect = new int[2];
1086eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
1096eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /** System accessibility manager, used to check state and send events. */
1106eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private final AccessibilityManager mManager;
1116eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
1126eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /** View whose internal structure is exposed through this helper. */
113049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private final View mHost;
1146eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
115049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /** Virtual node provider used to expose logical structure to services. */
116049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private MyNodeProvider mNodeProvider;
1176eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
118049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /** Identifier for the virtual view that holds accessibility focus. */
119049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private int mAccessibilityFocusedVirtualViewId = INVALID_ID;
1206eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
121049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /** Identifier for the virtual view that holds keyboard focus. */
122049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private int mKeyboardFocusedVirtualViewId = INVALID_ID;
123049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
124049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /** Identifier for the virtual view that is currently hovered. */
1256eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private int mHoveredVirtualViewId = INVALID_ID;
1266eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
1276eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
128049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Constructs a new helper that can expose a virtual view hierarchy for the
129049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * specified host view.
1306eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
131049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param host view whose virtual view hierarchy is exposed by this helper
1326eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
133049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public ExploreByTouchHelper(View host) {
134049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (host == null) {
1356eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            throw new IllegalArgumentException("View may not be null");
1366eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
1376eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
138049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        mHost = host;
139049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
140049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final Context context = host.getContext();
1416eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        mManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
142049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
143049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // Host view must be focusable so that we can delegate to virtual
144049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // views.
145049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        host.setFocusable(true);
1467f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        if (ViewCompat.getImportantForAccessibility(host)
1477f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette                == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1487f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette            ViewCompat.setImportantForAccessibility(
1497f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette                    host, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
150049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
1516eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
1526eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
1536eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    @Override
1546eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
1556eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if (mNodeProvider == null) {
156049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            mNodeProvider = new MyNodeProvider();
1576eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
1586eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        return mNodeProvider;
1596eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
1606eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
1616eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
162049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Delegates hover events from the host view.
163049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <p>
1646eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Dispatches hover {@link MotionEvent}s to the virtual view hierarchy when
1656eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * the Explore by Touch feature is enabled.
1666eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
167049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * This method should be called by overriding the host view's
168049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link View#dispatchHoverEvent(MotionEvent)} method:
1696eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <pre>&#64;Override
1706eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * public boolean dispatchHoverEvent(MotionEvent event) {
171049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *   return mHelper.dispatchHoverEvent(this, event)
172049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *       || super.dispatchHoverEvent(event);
1736eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * }
1746eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * </pre>
1756eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
1766eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param event The hover event to dispatch to the virtual view hierarchy.
1776eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @return Whether the hover event was handled.
1786eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
179049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final boolean dispatchHoverEvent(@NonNull MotionEvent event) {
1806eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if (!mManager.isEnabled()
1816eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                || !AccessibilityManagerCompat.isTouchExplorationEnabled(mManager)) {
1826eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            return false;
1836eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
1846eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
1856eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        switch (event.getAction()) {
1866ed40c1f86bcb172a1f0f069cde1c571a7781aeeAurimas Liutikas            case MotionEvent.ACTION_HOVER_MOVE:
1876ed40c1f86bcb172a1f0f069cde1c571a7781aeeAurimas Liutikas            case MotionEvent.ACTION_HOVER_ENTER:
1886eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                final int virtualViewId = getVirtualViewAt(event.getX(), event.getY());
1896eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                updateHoveredVirtualView(virtualViewId);
1906eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                return (virtualViewId != INVALID_ID);
1916ed40c1f86bcb172a1f0f069cde1c571a7781aeeAurimas Liutikas            case MotionEvent.ACTION_HOVER_EXIT:
192049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                if (mAccessibilityFocusedVirtualViewId != INVALID_ID) {
1936eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                    updateHoveredVirtualView(INVALID_ID);
1946eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                    return true;
1956eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                }
1966eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                return false;
1976eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            default:
1986eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                return false;
1996eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
2006eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
2016eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
2026eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
203049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Delegates key events from the host view.
204049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <p>
205049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * This method should be called by overriding the host view's
206049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link View#dispatchKeyEvent(KeyEvent)} method:
207049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <pre>&#64;Override
208049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * public boolean dispatchKeyEvent(KeyEvent event) {
209049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *   return mHelper.dispatchKeyEvent(event)
210049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *       || super.dispatchKeyEvent(event);
211049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * }
212049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * </pre>
213049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
214049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final boolean dispatchKeyEvent(@NonNull KeyEvent event) {
215049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        boolean handled = false;
216049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
217049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int action = event.getAction();
218049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (action != KeyEvent.ACTION_UP) {
219049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            final int keyCode = event.getKeyCode();
220049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            switch (keyCode) {
221049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                case KeyEvent.KEYCODE_DPAD_LEFT:
222049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                case KeyEvent.KEYCODE_DPAD_UP:
223049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                case KeyEvent.KEYCODE_DPAD_RIGHT:
224049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                case KeyEvent.KEYCODE_DPAD_DOWN:
2259c3a43f2608c2e7fceea46e4b500d68ce1c619ffAurimas Liutikas                    if (event.hasNoModifiers()) {
226049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        final int direction = keyToDirection(keyCode);
227049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        final int count = 1 + event.getRepeatCount();
228049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        for (int i = 0; i < count; i++) {
229049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                            if (moveFocus(direction, null)) {
230049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                                handled = true;
231049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                            } else {
232049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                                break;
233049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                            }
234049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        }
235049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    }
236049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    break;
237049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                case KeyEvent.KEYCODE_DPAD_CENTER:
238049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                case KeyEvent.KEYCODE_ENTER:
2399c3a43f2608c2e7fceea46e4b500d68ce1c619ffAurimas Liutikas                    if (event.hasNoModifiers()) {
240049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        if (event.getRepeatCount() == 0) {
241049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                            clickKeyboardFocusedVirtualView();
242049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                            handled = true;
243049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        }
244049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    }
245049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    break;
246049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                case KeyEvent.KEYCODE_TAB:
2479c3a43f2608c2e7fceea46e4b500d68ce1c619ffAurimas Liutikas                    if (event.hasNoModifiers()) {
248049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        handled = moveFocus(View.FOCUS_FORWARD, null);
2499c3a43f2608c2e7fceea46e4b500d68ce1c619ffAurimas Liutikas                    } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
250049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        handled = moveFocus(View.FOCUS_BACKWARD, null);
251049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    }
252049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    break;
253049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            }
254049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
255049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
256049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return handled;
257049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
258049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
259049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
260049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Delegates focus changes from the host view.
261049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <p>
262049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * This method should be called by overriding the host view's
263049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link View#onFocusChanged(boolean, int, Rect)} method:
264049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <pre>&#64;Override
265049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * public boolean onFocusChanged(boolean gainFocus, int direction,
266049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *     Rect previouslyFocusedRect) {
267049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *   super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
268049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *   mHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
269049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * }
270049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * </pre>
271049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
272049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final void onFocusChanged(boolean gainFocus, int direction,
273049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @Nullable Rect previouslyFocusedRect) {
274049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
275049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            clearKeyboardFocusForVirtualView(mKeyboardFocusedVirtualViewId);
276049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
277049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
278049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (gainFocus) {
279049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            moveFocus(direction, previouslyFocusedRect);
280049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
281049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
282049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
283049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
284049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return the identifier of the virtual view that has accessibility focus
285049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         or {@link #INVALID_ID} if no virtual view has accessibility
286049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         focus
287049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
288049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final int getAccessibilityFocusedVirtualViewId() {
289049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return mAccessibilityFocusedVirtualViewId;
290049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
291049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
292049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
293049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return the identifier of the virtual view that has keyboard focus
294049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         or {@link #INVALID_ID} if no virtual view has keyboard focus
295049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
296049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final int getKeyboardFocusedVirtualViewId() {
297049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return mKeyboardFocusedVirtualViewId;
298049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
299049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
300049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
301049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Maps key event codes to focus directions.
302049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
303049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param keyCode the key event code
304049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return the corresponding focus direction
305049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
306049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    @FocusRealDirection
307049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static int keyToDirection(int keyCode) {
308049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (keyCode) {
309049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case KeyEvent.KEYCODE_DPAD_LEFT:
310049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return View.FOCUS_LEFT;
311049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case KeyEvent.KEYCODE_DPAD_UP:
312049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return View.FOCUS_UP;
313049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case KeyEvent.KEYCODE_DPAD_RIGHT:
314049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return View.FOCUS_RIGHT;
315049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            default:
316049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return View.FOCUS_DOWN;
317049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
318049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
319049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
320049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
321049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Obtains the bounds for the specified virtual view.
322049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
323049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the identifier of the virtual view
324049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param outBounds the rect to populate with virtual view bounds
325049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
326049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private void getBoundsInParent(int virtualViewId, Rect outBounds) {
327049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final AccessibilityNodeInfoCompat node = obtainAccessibilityNodeInfo(virtualViewId);
328049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        node.getBoundsInParent(outBounds);
329049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
330049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
331049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
332049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Adapts AccessibilityNodeInfoCompat for obtaining bounds.
333049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
334049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static final FocusStrategy.BoundsAdapter<AccessibilityNodeInfoCompat> NODE_ADAPTER =
335049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            new FocusStrategy.BoundsAdapter<AccessibilityNodeInfoCompat>() {
336049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                @Override
337049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                public void obtainBounds(AccessibilityNodeInfoCompat node, Rect outBounds) {
338049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    node.getBoundsInParent(outBounds);
339049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                }
340049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            };
341049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
342049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
343e69072d0197b6360882acd76d4f0271727cfe5bcAlan Viverette     * Adapts SparseArrayCompat for iterating through values.
344049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
345e69072d0197b6360882acd76d4f0271727cfe5bcAlan Viverette    private static final FocusStrategy.CollectionAdapter<SparseArrayCompat<
346e69072d0197b6360882acd76d4f0271727cfe5bcAlan Viverette            AccessibilityNodeInfoCompat>, AccessibilityNodeInfoCompat> SPARSE_VALUES_ADAPTER =
347e69072d0197b6360882acd76d4f0271727cfe5bcAlan Viverette            new FocusStrategy.CollectionAdapter<SparseArrayCompat<
348e69072d0197b6360882acd76d4f0271727cfe5bcAlan Viverette                    AccessibilityNodeInfoCompat>, AccessibilityNodeInfoCompat>() {
349049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                @Override
350049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                public AccessibilityNodeInfoCompat get(
351e69072d0197b6360882acd76d4f0271727cfe5bcAlan Viverette                        SparseArrayCompat<AccessibilityNodeInfoCompat> collection, int index) {
352049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    return collection.valueAt(index);
353049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                }
354049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
355049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                @Override
356e69072d0197b6360882acd76d4f0271727cfe5bcAlan Viverette                public int size(SparseArrayCompat<AccessibilityNodeInfoCompat> collection) {
357049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    return collection.size();
358049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                }
359049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            };
360049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
361049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
362049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Attempts to move keyboard focus in the specified direction.
363049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
364049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param direction the direction in which to move keyboard focus
365049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param previouslyFocusedRect the bounds of the previously focused item,
366049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                              or {@code null} if not available
367049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return {@code true} if keyboard focus moved to a virtual view managed
368049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         by this helper, or {@code false} otherwise
369049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
370049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private boolean moveFocus(@FocusDirection int direction, @Nullable Rect previouslyFocusedRect) {
3711b327048091c92b5f46981792930aba4ab122c30Alan Viverette        final SparseArrayCompat<AccessibilityNodeInfoCompat> allNodes = getAllNodes();
3721b327048091c92b5f46981792930aba4ab122c30Alan Viverette
373049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int focusedNodeId = mKeyboardFocusedVirtualViewId;
374049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final AccessibilityNodeInfoCompat focusedNode =
3751b327048091c92b5f46981792930aba4ab122c30Alan Viverette                focusedNodeId == INVALID_ID ? null : allNodes.get(focusedNodeId);
376049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
377049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final AccessibilityNodeInfoCompat nextFocusedNode;
378049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
379049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_FORWARD:
380049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_BACKWARD:
381049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                final boolean isLayoutRtl =
382049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        ViewCompat.getLayoutDirection(mHost) == ViewCompat.LAYOUT_DIRECTION_RTL;
3831b327048091c92b5f46981792930aba4ab122c30Alan Viverette                nextFocusedNode = FocusStrategy.findNextFocusInRelativeDirection(allNodes,
384049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, direction, isLayoutRtl,
385049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        false);
386049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
387049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_LEFT:
388049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_UP:
389049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_RIGHT:
390049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_DOWN:
391049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                final Rect selectedRect = new Rect();
392049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
393049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    // Focus is moving from a virtual view within the host.
394049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    getBoundsInParent(mKeyboardFocusedVirtualViewId, selectedRect);
395049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                } else if (previouslyFocusedRect != null) {
396049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    // Focus is moving from a real view outside the host.
397049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    selectedRect.set(previouslyFocusedRect);
398049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                } else {
399049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    // Focus is moving from... somewhere? Make a guess.
400049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    // Usually this happens when another view was too lazy
401049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    // to pass the previously focused rect (ex. ScrollView
402049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    // when moving UP or DOWN).
403049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                    guessPreviouslyFocusedRect(mHost, direction, selectedRect);
404049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                }
4051b327048091c92b5f46981792930aba4ab122c30Alan Viverette                nextFocusedNode = FocusStrategy.findNextFocusInAbsoluteDirection(allNodes,
406049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, selectedRect, direction);
407049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
408049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            default:
409049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                throw new IllegalArgumentException("direction must be one of "
410049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        + "{FOCUS_FORWARD, FOCUS_BACKWARD, FOCUS_UP, FOCUS_DOWN, "
411049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        + "FOCUS_LEFT, FOCUS_RIGHT}.");
412049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
413049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
414049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int nextFocusedNodeId;
415049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (nextFocusedNode == null) {
416049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            nextFocusedNodeId = INVALID_ID;
417049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        } else {
4181b327048091c92b5f46981792930aba4ab122c30Alan Viverette            final int index = allNodes.indexOfValue(nextFocusedNode);
4191b327048091c92b5f46981792930aba4ab122c30Alan Viverette            nextFocusedNodeId = allNodes.keyAt(index);
420049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
421049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
422049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return requestKeyboardFocusForVirtualView(nextFocusedNodeId);
423049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
424049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
4251b327048091c92b5f46981792930aba4ab122c30Alan Viverette    private SparseArrayCompat<AccessibilityNodeInfoCompat> getAllNodes() {
4261b327048091c92b5f46981792930aba4ab122c30Alan Viverette        final List<Integer> virtualViewIds = new ArrayList<>();
4271b327048091c92b5f46981792930aba4ab122c30Alan Viverette        getVisibleVirtualViews(virtualViewIds);
4281b327048091c92b5f46981792930aba4ab122c30Alan Viverette
4291b327048091c92b5f46981792930aba4ab122c30Alan Viverette        final SparseArrayCompat<AccessibilityNodeInfoCompat> allNodes = new SparseArrayCompat<>();
4301b327048091c92b5f46981792930aba4ab122c30Alan Viverette        for (int virtualViewId = 0; virtualViewId < virtualViewIds.size(); virtualViewId++) {
4311b327048091c92b5f46981792930aba4ab122c30Alan Viverette            final AccessibilityNodeInfoCompat virtualView = createNodeForChild(virtualViewId);
4321b327048091c92b5f46981792930aba4ab122c30Alan Viverette            allNodes.put(virtualViewId, virtualView);
4331b327048091c92b5f46981792930aba4ab122c30Alan Viverette        }
4341b327048091c92b5f46981792930aba4ab122c30Alan Viverette
4351b327048091c92b5f46981792930aba4ab122c30Alan Viverette        return allNodes;
4361b327048091c92b5f46981792930aba4ab122c30Alan Viverette    }
4371b327048091c92b5f46981792930aba4ab122c30Alan Viverette
438049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
439049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Obtains a best guess for the previously focused rect for keyboard focus
440049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * moving in the specified direction.
441049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
442049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param host the view into which focus is moving
443049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param direction the absolute direction in which focus is moving
444049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param outBounds the rect to populate with the best-guess bounds for the
445049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                  previous focus rect
446049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
447049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static Rect guessPreviouslyFocusedRect(@NonNull View host,
448049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @FocusRealDirection int direction, @NonNull Rect outBounds) {
449049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int w = host.getWidth();
450049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int h = host.getHeight();
451049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
452049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
453049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_LEFT:
454049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                outBounds.set(w, 0, w, h);
455049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
456049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_UP:
457049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                outBounds.set(0, h, w, h);
458049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
459049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_RIGHT:
460049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                outBounds.set(-1, 0, -1, h);
461049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
462049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_DOWN:
463049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                outBounds.set(0, -1, w, -1);
464049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
465049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            default:
466049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                throw new IllegalArgumentException("direction must be one of "
467049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
468049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
469049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
470049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return outBounds;
471049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
472049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
473049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
474049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Performs a click action on the keyboard focused virtual view, if any.
475049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
476049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return {@code true} if the click action was performed successfully or
477049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         {@code false} otherwise
478049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
479049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private boolean clickKeyboardFocusedVirtualView() {
480049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return mKeyboardFocusedVirtualViewId != INVALID_ID && onPerformActionForVirtualView(
481049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                mKeyboardFocusedVirtualViewId, AccessibilityNodeInfoCompat.ACTION_CLICK, null);
482049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
483049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
484049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
4856eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Populates an event of the specified type with information about an item
4866eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * and attempts to send it up through the view hierarchy.
4876eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
4886eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * You should call this method after performing a user action that normally
4896eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * fires an accessibility event, such as clicking on an item.
490049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <p>
4916eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <pre>public void performItemClick(T item) {
4926eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *   ...
4936eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *   sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED);
4946eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * }
4956eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * </pre>
4966eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
497049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the identifier of the virtual view for which to
498049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      send an event
499049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param eventType the type of event to send
500049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return {@code true} if the event was sent successfully, {@code false}
501049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         otherwise
5026eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
503049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final boolean sendEventForVirtualView(int virtualViewId, int eventType) {
5046eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) {
5056eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            return false;
5066eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
5076eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
508049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final ViewParent parent = mHost.getParent();
5096eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if (parent == null) {
5106eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            return false;
5116eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
5126eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
5136eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        final AccessibilityEvent event = createEvent(virtualViewId, eventType);
514049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return ViewParentCompat.requestSendAccessibilityEvent(parent, mHost, event);
5156eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
5166eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
5176eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
5186eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Notifies the accessibility framework that the properties of the parent
5196eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * view have changed.
5206eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
521049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * You <strong>must</strong> call this method after adding or removing
522049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * items from the parent view.
5236eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
524049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final void invalidateRoot() {
525722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette        invalidateVirtualView(HOST_ID, AccessibilityEventCompat.CONTENT_CHANGE_TYPE_SUBTREE);
5266eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
5276eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
5286eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
5296eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Notifies the accessibility framework that the properties of a particular
5306eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * item have changed.
5316eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
532049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * You <strong>must</strong> call this method after changing any of the
533049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * properties set in
534049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
5356eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
536049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the virtual view id to invalidate, or
537049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      {@link #HOST_ID} to invalidate the root view
538722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     * @see #invalidateVirtualView(int, int)
5396eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
540049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final void invalidateVirtualView(int virtualViewId) {
541722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette        invalidateVirtualView(virtualViewId,
542722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette                AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED);
543722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette    }
544722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette
545722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette    /**
546722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     * Notifies the accessibility framework that the properties of a particular
547722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     * item have changed.
548722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     * <p>
549049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * You <strong>must</strong> call this method after changing any of the
550049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * properties set in
551049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
552722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     *
553049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the virtual view id to invalidate, or
554049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      {@link #HOST_ID} to invalidate the root view
555049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param changeTypes the bit mask of change types. May be {@code 0} for the
556722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     *                    default (undefined) change type or one or more of:
557722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     *         <ul>
558722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     *         <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
559722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     *         <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_SUBTREE}
560722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     *         <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_TEXT}
561722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     *         <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_UNDEFINED}
562722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     *         </ul>
563722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette     */
564049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final void invalidateVirtualView(int virtualViewId, int changeTypes) {
565722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette        if (virtualViewId != INVALID_ID && mManager.isEnabled()) {
566049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            final ViewParent parent = mHost.getParent();
567722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette            if (parent != null) {
568049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                // Send events up the hierarchy so they can be coalesced.
569722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette                final AccessibilityEvent event = createEvent(virtualViewId,
570722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette                        AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
571722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette                AccessibilityEventCompat.setContentChangeTypes(event, changeTypes);
572049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                ViewParentCompat.requestSendAccessibilityEvent(parent, mHost, event);
573722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette            }
574722514df962c62eee53f82df927b8969bc5ceb95Alan Viverette        }
5756eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
5766eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
5776eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
578049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Returns the virtual view ID for the currently accessibility focused
579049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * item.
580148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette     *
581049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return the identifier of the virtual view that has accessibility focus
582049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         or {@link #INVALID_ID} if no virtual view has accessibility
583049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         focus
584049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @deprecated Use {@link #getAccessibilityFocusedVirtualViewId()}.
585148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette     */
586049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    @Deprecated
587148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette    public int getFocusedVirtualView() {
588049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return getAccessibilityFocusedVirtualViewId();
589049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
590049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
591049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
592049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Called when the focus state of a virtual view changes.
593049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
594049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the virtual view identifier
595049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param hasFocus      {@code true} if the view has focus, {@code false}
596049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      otherwise
597049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
598049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    protected void onVirtualViewKeyboardFocusChanged(int virtualViewId, boolean hasFocus) {
599049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // Stub method.
600148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette    }
601148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette
602148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette    /**
6036eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Sets the currently hovered item, sending hover accessibility events as
6046eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * necessary to maintain the correct state.
6056eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
606049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the virtual view id for the item currently being
607049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      hovered, or {@link #INVALID_ID} if no item is
608049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      hovered within the parent view
6096eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
6106eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private void updateHoveredVirtualView(int virtualViewId) {
6116eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if (mHoveredVirtualViewId == virtualViewId) {
6126eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            return;
6136eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
6146eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
6156eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        final int previousVirtualViewId = mHoveredVirtualViewId;
6166eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        mHoveredVirtualViewId = virtualViewId;
6176eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
6186eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Stay consistent with framework behavior by sending ENTER/EXIT pairs
6196eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // in reverse order. This is accurate as of API 18.
62014d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas        sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
6216eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        sendEventForVirtualView(
62214d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas                previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
6236eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
6246eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
6256eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
6266eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Constructs and returns an {@link AccessibilityEvent} for the specified
62712d8b3f36c7b503221185b44791822e59e690966Alan Viverette     * virtual view id, which includes the host view ({@link #HOST_ID}).
6286eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
629049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the virtual view id for the item for which to
630049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      construct an event
631049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param eventType the type of event to construct
632049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return an {@link AccessibilityEvent} populated with information about
633049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         the specified item
6346eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
6356eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private AccessibilityEvent createEvent(int virtualViewId, int eventType) {
6366eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        switch (virtualViewId) {
63712d8b3f36c7b503221185b44791822e59e690966Alan Viverette            case HOST_ID:
6386eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                return createEventForHost(eventType);
6396eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            default:
6406eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                return createEventForChild(virtualViewId, eventType);
6416eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
6426eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
6436eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
6446eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
6456eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Constructs and returns an {@link AccessibilityEvent} for the host node.
6466eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
647049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param eventType the type of event to construct
648049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return an {@link AccessibilityEvent} populated with information about
649049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         the specified item
6506eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
6516eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private AccessibilityEvent createEventForHost(int eventType) {
6526eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
653049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        ViewCompat.onInitializeAccessibilityEvent(mHost, event);
6547f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        return event;
6557f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette    }
6567f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette
6577f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette    @Override
6587f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette    public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
6597f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        super.onInitializeAccessibilityEvent(host, event);
660fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette
661fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette        // Allow the client to populate the event.
662fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette        onPopulateEventForHost(event);
6636eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
6646eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
6656eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
6666eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Constructs and returns an {@link AccessibilityEvent} populated with
6676eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * information about the specified item.
6686eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
669049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the virtual view id for the item for which to
670049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      construct an event
671049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param eventType the type of event to construct
672049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return an {@link AccessibilityEvent} populated with information about
673049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         the specified item
6746eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
6756eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
6766eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
677049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final AccessibilityNodeInfoCompat node = obtainAccessibilityNodeInfo(virtualViewId);
678049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
679049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // Allow the client to override these properties,
68014d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas        event.getText().add(node.getText());
68114d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas        event.setContentDescription(node.getContentDescription());
68214d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas        event.setScrollable(node.isScrollable());
68314d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas        event.setPassword(node.isPassword());
68414d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas        event.setEnabled(node.isEnabled());
68514d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas        event.setChecked(node.isChecked());
6866eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
6876eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Allow the client to populate the event.
6886eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        onPopulateEventForVirtualView(virtualViewId, event);
6896eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
6906eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Make sure the developer is following the rules.
6916eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if (event.getText().isEmpty() && (event.getContentDescription() == null)) {
6926eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            throw new RuntimeException("Callbacks must add text or a content description in "
6936eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                    + "populateEventForVirtualViewId()");
6946eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
6956eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
6966eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Don't allow the client to override these properties.
69714d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas        event.setClassName(node.getClassName());
69814d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas        AccessibilityRecordCompat.setSource(event, mHost, virtualViewId);
699049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        event.setPackageName(mHost.getContext().getPackageName());
7006eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
7016eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        return event;
7026eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
7036eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
7046eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
705049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Obtains a populated {@link AccessibilityNodeInfoCompat} for the
706049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * virtual view with the specified identifier.
707049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <p>
708049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * This method may be called with identifier {@link #HOST_ID} to obtain a
709049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * node for the host view.
7106eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
711049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the identifier of the virtual view for which to
712049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      construct a node
713049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return an {@link AccessibilityNodeInfoCompat} populated with information
714049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *         about the specified item
7156eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
716049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    @NonNull
717540222c3675801eaa141ace1c164c4d3499b4721Aurimas Liutikas    AccessibilityNodeInfoCompat obtainAccessibilityNodeInfo(int virtualViewId) {
7181b327048091c92b5f46981792930aba4ab122c30Alan Viverette        if (virtualViewId == HOST_ID) {
7191b327048091c92b5f46981792930aba4ab122c30Alan Viverette            return createNodeForHost();
7206eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
721049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
7221b327048091c92b5f46981792930aba4ab122c30Alan Viverette        return createNodeForChild(virtualViewId);
7236eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
7246eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
7256eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
7266eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Constructs and returns an {@link AccessibilityNodeInfoCompat} for the
7276eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * host view populated with its virtual descendants.
7286eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
729049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return an {@link AccessibilityNodeInfoCompat} for the parent node
7306eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
7311fe6cc2f16121bc57f6a89aaa5502e47ab3e8fe9Alan Viverette    @NonNull
7326eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private AccessibilityNodeInfoCompat createNodeForHost() {
7337f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(mHost);
7347f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        ViewCompat.onInitializeAccessibilityNodeInfo(mHost, info);
73512d8b3f36c7b503221185b44791822e59e690966Alan Viverette
7366eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Add the virtual descendants.
737049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final ArrayList<Integer> virtualViewIds = new ArrayList<>();
7386eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        getVisibleVirtualViews(virtualViewIds);
7397f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette
7407f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        final int realNodeCount = info.getChildCount();
741fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette        if (realNodeCount > 0 && virtualViewIds.size() > 0) {
742fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette            throw new RuntimeException("Views cannot have both real and virtual children");
743fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette        }
7446eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
745049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        for (int i = 0, count = virtualViewIds.size(); i < count; i++) {
7467f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette            info.addChild(mHost, virtualViewIds.get(i));
7476eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
7486eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
7497f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        return info;
7507f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette    }
7517f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette
7527f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette    @Override
7537f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
7547f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        super.onInitializeAccessibilityNodeInfo(host, info);
7557f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette
7567f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        // Allow the client to populate the host node.
7577f62b32cd8fe966a5b1eaa850fb6595347a2564bAlan Viverette        onPopulateNodeForHost(info);
7586eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
7596eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
7606eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
7616eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Constructs and returns an {@link AccessibilityNodeInfoCompat} for the
7626eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * specified item. Automatically manages accessibility focus actions.
7636eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
7646eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Allows the implementing class to specify most node properties, but
7656eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * overrides the following:
7666eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <ul>
7676eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>{@link AccessibilityNodeInfoCompat#setPackageName}
7686eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>{@link AccessibilityNodeInfoCompat#setClassName}
7696eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>{@link AccessibilityNodeInfoCompat#setParent(View)}
7706eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>{@link AccessibilityNodeInfoCompat#setSource(View, int)}
7716eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>{@link AccessibilityNodeInfoCompat#setVisibleToUser}
7726eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>{@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)}
7736eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * </ul>
7746eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
7756eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Uses the bounds of the parent view and the parent-relative bounding
7766eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * rectangle specified by
7776eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link AccessibilityNodeInfoCompat#getBoundsInParent} to automatically
7786eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * update the following properties:
7796eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <ul>
7806eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>{@link AccessibilityNodeInfoCompat#setVisibleToUser}
7816eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>{@link AccessibilityNodeInfoCompat#setBoundsInParent}
7826eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * </ul>
7836eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
784049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the virtual view id for item for which to construct
785049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      a node
786049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return an {@link AccessibilityNodeInfoCompat} for the specified item
7876eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
7881fe6cc2f16121bc57f6a89aaa5502e47ab3e8fe9Alan Viverette    @NonNull
7896eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private AccessibilityNodeInfoCompat createNodeForChild(int virtualViewId) {
7906eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain();
7916eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
7926eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Ensure the client has good defaults.
7936eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        node.setEnabled(true);
794049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        node.setFocusable(true);
7956eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        node.setClassName(DEFAULT_CLASS_NAME);
796cc35612e283564c245408da4bb9264f38a02487aAlan Viverette        node.setBoundsInParent(INVALID_PARENT_BOUNDS);
7970906dafe25770e872a72f8abd2c044b0faef86d7Alan Viverette        node.setBoundsInScreen(INVALID_PARENT_BOUNDS);
79846f49c1900fc65a3e3bc0057e28161be6230e1daZach Kuznia        node.setParent(mHost);
7996eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
8006eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Allow the client to populate the node.
8016eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        onPopulateNodeForVirtualView(virtualViewId, node);
8026eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
8036eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Make sure the developer is following the rules.
8046eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if ((node.getText() == null) && (node.getContentDescription() == null)) {
8056eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            throw new RuntimeException("Callbacks must add text or a content description in "
8066eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                    + "populateNodeForVirtualViewId()");
8076eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
8086eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
8096eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        node.getBoundsInParent(mTempParentRect);
810cc35612e283564c245408da4bb9264f38a02487aAlan Viverette        if (mTempParentRect.equals(INVALID_PARENT_BOUNDS)) {
8116eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            throw new RuntimeException("Callbacks must set parent bounds in "
8126eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                    + "populateNodeForVirtualViewId()");
8136eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
8146eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
8156eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        final int actions = node.getActions();
8166eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if ((actions & AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS) != 0) {
8176eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            throw new RuntimeException("Callbacks must not add ACTION_ACCESSIBILITY_FOCUS in "
8186eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                    + "populateNodeForVirtualViewId()");
8196eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
8206eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if ((actions & AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS) != 0) {
8216eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            throw new RuntimeException("Callbacks must not add ACTION_CLEAR_ACCESSIBILITY_FOCUS in "
8226eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                    + "populateNodeForVirtualViewId()");
8236eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
8246eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
8256eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Don't allow the client to override these properties.
826049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        node.setPackageName(mHost.getContext().getPackageName());
827049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        node.setSource(mHost, virtualViewId);
8286eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
8296eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Manage internal accessibility focus state.
830049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (mAccessibilityFocusedVirtualViewId == virtualViewId) {
8316eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            node.setAccessibilityFocused(true);
8326eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
8336eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        } else {
8346eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            node.setAccessibilityFocused(false);
8356eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            node.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
8366eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
8376eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
838049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // Manage internal keyboard focus state.
839049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final boolean isFocused = mKeyboardFocusedVirtualViewId == virtualViewId;
840049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (isFocused) {
841049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS);
842049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        } else if (node.isFocusable()) {
843049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            node.addAction(AccessibilityNodeInfoCompat.ACTION_FOCUS);
844049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
845049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        node.setFocused(isFocused);
846049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
8473b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia        mHost.getLocationOnScreen(mTempGlobalRect);
8486eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
8490906dafe25770e872a72f8abd2c044b0faef86d7Alan Viverette        // If not explicitly specified, calculate screen-relative bounds and
8500906dafe25770e872a72f8abd2c044b0faef86d7Alan Viverette        // offset for scroll position based on bounds in parent.
8510906dafe25770e872a72f8abd2c044b0faef86d7Alan Viverette        node.getBoundsInScreen(mTempScreenRect);
8520906dafe25770e872a72f8abd2c044b0faef86d7Alan Viverette        if (mTempScreenRect.equals(INVALID_PARENT_BOUNDS)) {
8530906dafe25770e872a72f8abd2c044b0faef86d7Alan Viverette            node.getBoundsInParent(mTempScreenRect);
8543b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia
8553b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia            // If there is a parent node, adjust bounds based on the parent node.
8563b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia            if (node.mParentVirtualDescendantId != HOST_ID) {
8573b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                AccessibilityNodeInfoCompat parentNode = AccessibilityNodeInfoCompat.obtain();
8583b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                // Walk up the node tree to adjust the screen rect.
8593b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                for (int virtualDescendantId = node.mParentVirtualDescendantId;
8603b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                        virtualDescendantId != HOST_ID;
8613b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                        virtualDescendantId = parentNode.mParentVirtualDescendantId) {
8623b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                    // Reset the values in the parent node we'll be using.
8633b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                    parentNode.setParent(mHost, HOST_ID);
8643b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                    parentNode.setBoundsInParent(INVALID_PARENT_BOUNDS);
8653b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                    // Adjust the bounds for the parent node.
8663b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                    onPopulateNodeForVirtualView(virtualDescendantId, parentNode);
8673b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                    parentNode.getBoundsInParent(mTempParentRect);
8683b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                    mTempScreenRect.offset(mTempParentRect.left, mTempParentRect.top);
8693b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                }
8703b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                parentNode.recycle();
8713b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia            }
8723b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia            // Adjust the rect for the host view's location.
8730906dafe25770e872a72f8abd2c044b0faef86d7Alan Viverette            mTempScreenRect.offset(mTempGlobalRect[0] - mHost.getScrollX(),
8740906dafe25770e872a72f8abd2c044b0faef86d7Alan Viverette                    mTempGlobalRect[1] - mHost.getScrollY());
8753b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia        }
8763b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia
8773b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia        if (mHost.getLocalVisibleRect(mTempVisibleRect)) {
8783b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia            mTempVisibleRect.offset(mTempGlobalRect[0] - mHost.getScrollX(),
8793b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia                    mTempGlobalRect[1] - mHost.getScrollY());
8803236f136c34c3db9b05c69cdbaccdc0fa6203a1bAurimas Liutikas            final boolean intersects = mTempScreenRect.intersect(mTempVisibleRect);
8813236f136c34c3db9b05c69cdbaccdc0fa6203a1bAurimas Liutikas            if (intersects) {
8823236f136c34c3db9b05c69cdbaccdc0fa6203a1bAurimas Liutikas                node.setBoundsInScreen(mTempScreenRect);
8833b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia
8843236f136c34c3db9b05c69cdbaccdc0fa6203a1bAurimas Liutikas                if (isVisibleToUser(mTempScreenRect)) {
8853236f136c34c3db9b05c69cdbaccdc0fa6203a1bAurimas Liutikas                    node.setVisibleToUser(true);
8863236f136c34c3db9b05c69cdbaccdc0fa6203a1bAurimas Liutikas                }
8873b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia            }
8880906dafe25770e872a72f8abd2c044b0faef86d7Alan Viverette        }
8896eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
8906eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        return node;
8916eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
8926eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
893540222c3675801eaa141ace1c164c4d3499b4721Aurimas Liutikas    boolean performAction(int virtualViewId, int action, Bundle arguments) {
8946eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        switch (virtualViewId) {
89512d8b3f36c7b503221185b44791822e59e690966Alan Viverette            case HOST_ID:
8966eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                return performActionForHost(action, arguments);
8976eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            default:
8986eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                return performActionForChild(virtualViewId, action, arguments);
8996eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
9006eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
9016eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
9026eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private boolean performActionForHost(int action, Bundle arguments) {
903049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return ViewCompat.performAccessibilityAction(mHost, action, arguments);
9046eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
9056eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
9066eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) {
9076eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        switch (action) {
9086eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
909148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette                return requestAccessibilityFocus(virtualViewId);
9106eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
911148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette                return clearAccessibilityFocus(virtualViewId);
912049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case AccessibilityNodeInfoCompat.ACTION_FOCUS:
913049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return requestKeyboardFocusForVirtualView(virtualViewId);
914049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS:
915049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return clearKeyboardFocusForVirtualView(virtualViewId);
9166eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            default:
917049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return onPerformActionForVirtualView(virtualViewId, action, arguments);
9186eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
9196eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
9206eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
9216eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
9226eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Computes whether the specified {@link Rect} intersects with the visible
9236eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * portion of its parent {@link View}. Modifies {@code localRect} to contain
9246eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * only the visible portion.
9256eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
926049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param localRect a rectangle in local (parent) coordinates
927049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return whether the specified {@link Rect} is visible on the screen
9286eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
9293b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia    private boolean isVisibleToUser(Rect localRect) {
9306eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Missing or empty bounds mean this view is not visible.
9316eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if ((localRect == null) || localRect.isEmpty()) {
9326eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            return false;
9336eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
9346eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
9356eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // Attached to invisible window means this view is not visible.
936049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (mHost.getWindowVisibility() != View.VISIBLE) {
9376eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            return false;
9386eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
9396eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
9406eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // An invisible predecessor means that this view is not visible.
941049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        ViewParent viewParent = mHost.getParent();
9426eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        while (viewParent instanceof View) {
9436eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            final View view = (View) viewParent;
9446eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            if ((ViewCompat.getAlpha(view) <= 0) || (view.getVisibility() != View.VISIBLE)) {
9456eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                return false;
9466eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            }
9476eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            viewParent = view.getParent();
9486eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
9496eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
9506eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // A null parent implies the view is not visible.
9513b591130c68223aa613660aaff79e65e26b5fc06Zach Kuznia        return viewParent != null;
9526eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
9536eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
9546eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
9556eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Attempts to give accessibility focus to a virtual view.
9566eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
9576eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * A virtual view will not actually take focus if
9586eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link AccessibilityManager#isEnabled()} returns false,
9596eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link AccessibilityManager#isTouchExplorationEnabled()} returns false,
9606eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * or the view already has accessibility focus.
9616eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
962049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the identifier of the virtual view on which to
963049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      place accessibility focus
964049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return whether this virtual view actually took accessibility focus
9656eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
9666eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    private boolean requestAccessibilityFocus(int virtualViewId) {
9676eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        if (!mManager.isEnabled()
9686eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                || !AccessibilityManagerCompat.isTouchExplorationEnabled(mManager)) {
9696eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            return false;
9706eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
9716eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        // TODO: Check virtual view visibility.
972049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (mAccessibilityFocusedVirtualViewId != virtualViewId) {
973f6a201aaad218a0ee66a9479b16b012a3fd108eaAlan Viverette            // Clear focus from the previously focused view, if applicable.
974049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            if (mAccessibilityFocusedVirtualViewId != INVALID_ID) {
975049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                clearAccessibilityFocus(mAccessibilityFocusedVirtualViewId);
976f6a201aaad218a0ee66a9479b16b012a3fd108eaAlan Viverette            }
977f6a201aaad218a0ee66a9479b16b012a3fd108eaAlan Viverette
978f6a201aaad218a0ee66a9479b16b012a3fd108eaAlan Viverette            // Set focus on the new view.
979049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            mAccessibilityFocusedVirtualViewId = virtualViewId;
980f6a201aaad218a0ee66a9479b16b012a3fd108eaAlan Viverette
9816eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            // TODO: Only invalidate virtual view bounds.
982049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            mHost.invalidate();
9836eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            sendEventForVirtualView(virtualViewId,
9846eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                    AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
9856eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            return true;
9866eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
9876eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        return false;
9886eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
9896eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
9906eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
9916eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Attempts to clear accessibility focus from a virtual view.
992148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette     *
993049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the identifier of the virtual view from which to
994049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      clear accessibility focus
995049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return whether this virtual view actually cleared accessibility focus
9966eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
997148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette    private boolean clearAccessibilityFocus(int virtualViewId) {
998049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (mAccessibilityFocusedVirtualViewId == virtualViewId) {
999049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            mAccessibilityFocusedVirtualViewId = INVALID_ID;
1000049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            mHost.invalidate();
10016eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            sendEventForVirtualView(virtualViewId,
10026eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette                    AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
1003148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette            return true;
10046eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
1005148d85f65c0f01be809032d2140c7df6d7275504Alan Viverette        return false;
10066eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
10076eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
10086eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
1009049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Attempts to give keyboard focus to a virtual view.
1010049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
1011049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the identifier of the virtual view on which to
1012049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      place keyboard focus
1013049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return whether this virtual view actually took keyboard focus
1014049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
1015049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final boolean requestKeyboardFocusForVirtualView(int virtualViewId) {
1016049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (!mHost.isFocused() && !mHost.requestFocus()) {
1017049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            // Host must have real keyboard focus.
1018049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return false;
1019049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
1020049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1021049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (mKeyboardFocusedVirtualViewId == virtualViewId) {
1022049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            // The virtual view already has focus.
1023049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return false;
1024049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
1025049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1026049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
1027049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            clearKeyboardFocusForVirtualView(mKeyboardFocusedVirtualViewId);
1028049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
1029049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1030049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        mKeyboardFocusedVirtualViewId = virtualViewId;
1031049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1032049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        onVirtualViewKeyboardFocusChanged(virtualViewId, true);
1033049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
1034049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1035049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return true;
1036049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
1037049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1038049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
1039049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Attempts to clear keyboard focus from a virtual view.
1040049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
1041049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param virtualViewId the identifier of the virtual view from which to
1042049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *                      clear keyboard focus
1043049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return whether this virtual view actually cleared keyboard focus
1044049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
1045049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public final boolean clearKeyboardFocusForVirtualView(int virtualViewId) {
1046049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (mKeyboardFocusedVirtualViewId != virtualViewId) {
1047049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            // The virtual view is not focused.
1048049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return false;
1049049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
1050049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1051049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        mKeyboardFocusedVirtualViewId = INVALID_ID;
1052049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1053049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        onVirtualViewKeyboardFocusChanged(virtualViewId, false);
1054049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
1055049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1056049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return true;
1057049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
1058049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
1059049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
10606eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Provides a mapping between view-relative coordinates and logical
10616eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * items.
10626eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
10636eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param x The view-relative x coordinate
10646eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param y The view-relative y coordinate
10656eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @return virtual view identifier for the logical item under
106612d8b3f36c7b503221185b44791822e59e690966Alan Viverette     *         coordinates (x,y) or {@link #HOST_ID} if there is no item at
1067cd9978cdf0c3852189fb881846db6569d43f5598Alan Viverette     *         the given coordinates
10686eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
10696eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    protected abstract int getVirtualViewAt(float x, float y);
10706eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
10716eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
10726eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Populates a list with the view's visible items. The ordering of items
10736eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * within {@code virtualViewIds} specifies order of accessibility focus
10746eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * traversal.
10756eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
10766eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param virtualViewIds The list to populate with visible items
10776eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
10786eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    protected abstract void getVisibleVirtualViews(List<Integer> virtualViewIds);
10796eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
10806eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
10816eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Populates an {@link AccessibilityEvent} with information about the
10826eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * specified item.
10836eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
1084049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * The helper class automatically populates the following fields based on
1085049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * the values set by
1086049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)},
1087049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * but implementations may optionally override them:
10886eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <ul>
1089049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>event text, see {@link AccessibilityEvent#getText()}
1090049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>content description, see
1091049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityEvent#setContentDescription(CharSequence)}
1092049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>scrollability, see {@link AccessibilityEvent#setScrollable(boolean)}
1093049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>password state, see {@link AccessibilityEvent#setPassword(boolean)}
1094049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>enabled state, see {@link AccessibilityEvent#setEnabled(boolean)}
1095049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>checked state, see {@link AccessibilityEvent#setChecked(boolean)}
10966eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * </ul>
10976eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
10986eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * The following required fields are automatically populated by the
10996eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * helper class and may not be overridden:
11006eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <ul>
1101049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>item class name, set to the value used in
1102049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}
11036eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>package name, set to the package of the host view's
11046eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link Context}, see {@link AccessibilityEvent#setPackageName}
11056eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>event source, set to the host view and virtual view identifier,
110614d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas     * see {@link AccessibilityRecordCompat#setSource(AccessibilityRecord, View, int)}
11076eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * </ul>
11086eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
11096eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param virtualViewId The virtual view id for the item for which to
11106eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *            populate the event
11116eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param event The event to populate
11126eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
1113049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
1114049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // Default implementation is no-op.
1115049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
11166eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
11176eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
1118fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     * Populates an {@link AccessibilityEvent} with information about the host
1119fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     * view.
1120fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     * <p>
1121fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     * The default implementation is a no-op.
1122fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     *
1123fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     * @param event the event to populate with information about the host view
1124fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     */
1125fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette    protected void onPopulateEventForHost(AccessibilityEvent event) {
1126fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette        // Default implementation is no-op.
1127fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette    }
1128fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette
1129fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette    /**
11306eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Populates an {@link AccessibilityNodeInfoCompat} with information
11316eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * about the specified item.
11326eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
1133049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Implementations <strong>must</strong> populate the following required
1134049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * fields:
11356eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <ul>
1136049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>event text, see
1137049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#setText(CharSequence)} or
1138049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#setContentDescription(CharSequence)}
11396eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>bounds in parent coordinates, see
1140049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#setBoundsInParent(Rect)}
11416eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * </ul>
11426eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
11436eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * The helper class automatically populates the following fields with
11446eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * default values, but implementations may optionally override them:
11456eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <ul>
1146049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>enabled state, set to {@code true}, see
1147049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#setEnabled(boolean)}
1148049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>keyboard focusability, set to {@code true}, see
1149049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#setFocusable(boolean)}
1150049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>item class name, set to {@code android.view.View}, see
1151049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#setClassName(CharSequence)}
11526eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * </ul>
11536eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
11546eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * The following required fields are automatically populated by the
11556eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * helper class and may not be overridden:
11566eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <ul>
11576eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>package name, identical to the package name set by
1158049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link #onPopulateEventForVirtualView(int, AccessibilityEvent)}, see
11596eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link AccessibilityNodeInfoCompat#setPackageName}
11606eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>node source, identical to the event source set in
1161049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link #onPopulateEventForVirtualView(int, AccessibilityEvent)}, see
11626eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link AccessibilityNodeInfoCompat#setSource(View, int)}
11636eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>parent view, set to the host view, see
11646eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link AccessibilityNodeInfoCompat#setParent(View)}
11656eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>visibility, computed based on parent-relative bounds, see
1166049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#setVisibleToUser(boolean)}
11676eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>accessibility focus, computed based on internal helper state, see
1168049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#setAccessibilityFocused(boolean)}
1169049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <li>keyboard focus, computed based on internal helper state, see
1170049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#setFocused(boolean)}
11716eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <li>bounds in screen coordinates, computed based on host view bounds,
1172049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * see {@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)}
11736eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * </ul>
11746eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
1175049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Additionally, the helper class automatically handles keyboard focus and
1176049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * accessibility focus management by adding the appropriate
1177049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#ACTION_FOCUS},
1178049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_FOCUS},
1179049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS}, or
11806eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
1181049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * actions. Implementations must <strong>never</strong> manually add these
1182049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * actions.
11836eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
11846eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * The helper class also automatically modifies parent- and
11856eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * screen-relative bounds to reflect the portion of the item visible
11866eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * within its parent.
11876eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
11886eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param virtualViewId The virtual view identifier of the item for
11896eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *            which to populate the node
11906eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param node The node to populate
11916eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
11926eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    protected abstract void onPopulateNodeForVirtualView(
11936eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            int virtualViewId, AccessibilityNodeInfoCompat node);
11946eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
11956eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
119612d8b3f36c7b503221185b44791822e59e690966Alan Viverette     * Populates an {@link AccessibilityNodeInfoCompat} with information
119712d8b3f36c7b503221185b44791822e59e690966Alan Viverette     * about the host view.
119812d8b3f36c7b503221185b44791822e59e690966Alan Viverette     * <p>
1199fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     * The default implementation is a no-op.
1200fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     *
1201fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette     * @param node the node to populate with information about the host view
120212d8b3f36c7b503221185b44791822e59e690966Alan Viverette     */
1203fe107ccc9e0ee09fbf7bcb9d17cee970b10ee0cfAlan Viverette    protected void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
120412d8b3f36c7b503221185b44791822e59e690966Alan Viverette        // Default implementation is no-op.
120512d8b3f36c7b503221185b44791822e59e690966Alan Viverette    }
120612d8b3f36c7b503221185b44791822e59e690966Alan Viverette
120712d8b3f36c7b503221185b44791822e59e690966Alan Viverette    /**
12086eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * Performs the specified accessibility action on the item associated
12096eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * with the virtual view identifier. See
12106eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link AccessibilityNodeInfoCompat#performAction(int, Bundle)} for
12116eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * more information.
12126eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
1213049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Implementations <strong>must</strong> handle any actions added manually
1214049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * in
1215049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
12166eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * <p>
12176eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * The helper class automatically handles focus management resulting
12186eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * from {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS}
12196eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * and
12206eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
12216eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * actions.
12226eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *
12236eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param virtualViewId The virtual view identifier of the item on which
12246eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *            to perform the action
12256eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param action The accessibility action to perform
12266eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @param arguments (Optional) A bundle with additional arguments, or
12276eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     *            null
12286eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     * @return true if the action was performed
12296eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
12306eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    protected abstract boolean onPerformActionForVirtualView(
12316eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            int virtualViewId, int action, Bundle arguments);
12326eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
12336eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    /**
1234049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Exposes a virtual view hierarchy to the accessibility framework.
12356eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette     */
1236049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private class MyNodeProvider extends AccessibilityNodeProviderCompat {
1237540222c3675801eaa141ace1c164c4d3499b4721Aurimas Liutikas        MyNodeProvider() {
1238540222c3675801eaa141ace1c164c4d3499b4721Aurimas Liutikas        }
1239540222c3675801eaa141ace1c164c4d3499b4721Aurimas Liutikas
12406eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        @Override
12416eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
12427c8264ea497e54aa1536c76e553b52d2207a50fbAlan Viverette            // The caller takes ownership of the node and is expected to
12437c8264ea497e54aa1536c76e553b52d2207a50fbAlan Viverette            // recycle it when done, so always return a copy.
12447c8264ea497e54aa1536c76e553b52d2207a50fbAlan Viverette            final AccessibilityNodeInfoCompat node =
12457c8264ea497e54aa1536c76e553b52d2207a50fbAlan Viverette                    ExploreByTouchHelper.this.obtainAccessibilityNodeInfo(virtualViewId);
12467c8264ea497e54aa1536c76e553b52d2207a50fbAlan Viverette            return AccessibilityNodeInfoCompat.obtain(node);
12476eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
12486eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette
12496eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        @Override
12506eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        public boolean performAction(int virtualViewId, int action, Bundle arguments) {
12516eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette            return ExploreByTouchHelper.this.performAction(virtualViewId, action, arguments);
12526eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette        }
12535c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver
12545c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver        @Override
12555c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver        public AccessibilityNodeInfoCompat findFocus(int focusType) {
12565c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver            int focusedId = (focusType == AccessibilityNodeInfoCompat.FOCUS_ACCESSIBILITY) ?
12575c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver                    mAccessibilityFocusedVirtualViewId : mKeyboardFocusedVirtualViewId;
12585c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver            if (focusedId == INVALID_ID) {
12595c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver                return null;
12605c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver            }
12615c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver            return createAccessibilityNodeInfo(focusedId);
12625c0b1da91a9f27f85113990f99f6844030e8c9d8Phil Weaver        }
12636eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette    }
12646eb3cdf42d5382aef6b6a6afd7c305dbc27885b9Alan Viverette}
1265