KeyboardAccessibilityNodeProvider.java revision 9a81ce92c381007affe6bb2310bf94c9856eaae1
19a81ce92c381007affe6bb2310bf94c9856eaae1alanv/*
29a81ce92c381007affe6bb2310bf94c9856eaae1alanv * Copyright (C) 2012 The Android Open Source Project
39a81ce92c381007affe6bb2310bf94c9856eaae1alanv *
49a81ce92c381007affe6bb2310bf94c9856eaae1alanv * Licensed under the Apache License, Version 2.0 (the "License");
59a81ce92c381007affe6bb2310bf94c9856eaae1alanv * you may not use this file except in compliance with the License.
69a81ce92c381007affe6bb2310bf94c9856eaae1alanv * You may obtain a copy of the License at
79a81ce92c381007affe6bb2310bf94c9856eaae1alanv *
89a81ce92c381007affe6bb2310bf94c9856eaae1alanv *      http://www.apache.org/licenses/LICENSE-2.0
99a81ce92c381007affe6bb2310bf94c9856eaae1alanv *
109a81ce92c381007affe6bb2310bf94c9856eaae1alanv * Unless required by applicable law or agreed to in writing, software
119a81ce92c381007affe6bb2310bf94c9856eaae1alanv * distributed under the License is distributed on an "AS IS" BASIS,
129a81ce92c381007affe6bb2310bf94c9856eaae1alanv * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139a81ce92c381007affe6bb2310bf94c9856eaae1alanv * See the License for the specific language governing permissions and
149a81ce92c381007affe6bb2310bf94c9856eaae1alanv * limitations under the License.
159a81ce92c381007affe6bb2310bf94c9856eaae1alanv */
169a81ce92c381007affe6bb2310bf94c9856eaae1alanv
179a81ce92c381007affe6bb2310bf94c9856eaae1alanvpackage com.android.inputmethod.accessibility;
189a81ce92c381007affe6bb2310bf94c9856eaae1alanv
199a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.graphics.Rect;
209a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.inputmethodservice.InputMethodService;
219a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.ViewCompat;
229a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
239a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
249a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.accessibility.AccessibilityRecordCompat;
259a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.util.Log;
269a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.util.SparseArray;
279a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.View;
289a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.ViewTreeObserver.OnGlobalLayoutListener;
299a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.accessibility.AccessibilityEvent;
309a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.inputmethod.EditorInfo;
319a81ce92c381007affe6bb2310bf94c9856eaae1alanv
329a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport com.android.inputmethod.keyboard.Key;
339a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport com.android.inputmethod.keyboard.Keyboard;
349a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport com.android.inputmethod.keyboard.KeyboardView;
359a81ce92c381007affe6bb2310bf94c9856eaae1alanv
369a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport java.util.Collections;
379a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport java.util.LinkedList;
389a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport java.util.List;
399a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport java.util.Set;
409a81ce92c381007affe6bb2310bf94c9856eaae1alanv
419a81ce92c381007affe6bb2310bf94c9856eaae1alanv/**
429a81ce92c381007affe6bb2310bf94c9856eaae1alanv * Exposes a virtual view sub-tree for {@link KeyboardView} and generates
439a81ce92c381007affe6bb2310bf94c9856eaae1alanv * {@link AccessibilityEvent}s for individual {@link Key}s.
449a81ce92c381007affe6bb2310bf94c9856eaae1alanv * <p>
459a81ce92c381007affe6bb2310bf94c9856eaae1alanv * A virtual sub-tree is composed of imaginary {@link View}s that are reported
469a81ce92c381007affe6bb2310bf94c9856eaae1alanv * as a part of the view hierarchy for accessibility purposes. This enables
479a81ce92c381007affe6bb2310bf94c9856eaae1alanv * custom views that draw complex content to report them selves as a tree of
489a81ce92c381007affe6bb2310bf94c9856eaae1alanv * virtual views, thus conveying their logical structure.
499a81ce92c381007affe6bb2310bf94c9856eaae1alanv * </p>
509a81ce92c381007affe6bb2310bf94c9856eaae1alanv */
519a81ce92c381007affe6bb2310bf94c9856eaae1alanvpublic class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
529a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
539a81ce92c381007affe6bb2310bf94c9856eaae1alanv
549a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final KeyboardView mKeyboardView;
559a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final InputMethodService mInputMethodService;
569a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper;
579a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final AccessibilityUtils mAccessibilityUtils;
589a81ce92c381007affe6bb2310bf94c9856eaae1alanv
599a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /** A map of integer IDs to {@link Key}s. */
609a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>();
619a81ce92c381007affe6bb2310bf94c9856eaae1alanv
629a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /** Temporary rect used to calculate in-screen bounds. */
639a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final Rect mTempBoundsInScreen = new Rect();
649a81ce92c381007affe6bb2310bf94c9856eaae1alanv
659a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /** The parent view's cached on-screen location. */
669a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final int[] mParentLocation = new int[2];
679a81ce92c381007affe6bb2310bf94c9856eaae1alanv
689a81ce92c381007affe6bb2310bf94c9856eaae1alanv    public AccessibilityEntityProvider(KeyboardView keyboardView, InputMethodService inputMethod) {
699a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mKeyboardView = keyboardView;
709a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mInputMethodService = inputMethod;
719a81ce92c381007affe6bb2310bf94c9856eaae1alanv
729a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance();
739a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mAccessibilityUtils = AccessibilityUtils.getInstance();
749a81ce92c381007affe6bb2310bf94c9856eaae1alanv
759a81ce92c381007affe6bb2310bf94c9856eaae1alanv        assignVirtualViewIds();
769a81ce92c381007affe6bb2310bf94c9856eaae1alanv        updateParentLocation();
779a81ce92c381007affe6bb2310bf94c9856eaae1alanv
789a81ce92c381007affe6bb2310bf94c9856eaae1alanv        // Ensure that the on-screen bounds are cleared when the layout changes.
799a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mKeyboardView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
809a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
819a81ce92c381007affe6bb2310bf94c9856eaae1alanv
829a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
839a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Creates and populates an {@link AccessibilityEvent} for the specified key
849a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * and event type.
859a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
869a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key A key on the host keyboard view.
879a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param eventType The event type to create.
889a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return A populated {@link AccessibilityEvent} for the key.
899a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @see AccessibilityEvent
909a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
919a81ce92c381007affe6bb2310bf94c9856eaae1alanv    public AccessibilityEvent createAccessibilityEvent(Key key, int eventType) {
929a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final int virtualViewId = generateVirtualViewIdForKey(key);
939a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final String keyDescription = getKeyDescription(key);
949a81ce92c381007affe6bb2310bf94c9856eaae1alanv
959a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
969a81ce92c381007affe6bb2310bf94c9856eaae1alanv        event.setPackageName(mKeyboardView.getContext().getPackageName());
979a81ce92c381007affe6bb2310bf94c9856eaae1alanv        event.setClassName(key.getClass().getName());
989a81ce92c381007affe6bb2310bf94c9856eaae1alanv        event.getText().add(keyDescription);
999a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1009a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event);
1019a81ce92c381007affe6bb2310bf94c9856eaae1alanv        record.setSource(mKeyboardView, virtualViewId);
1029a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1039a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return event;
1049a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
1059a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1069a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
1079a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual
1089a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or
1099a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}.
1109a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * <p>
1119a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * A virtual descendant is an imaginary View that is reported as a part of
1129a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * the view hierarchy for accessibility purposes. This enables custom views
1139a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * that draw complex content to report them selves as a tree of virtual
1149a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * views, thus conveying their logical structure.
1159a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * </p>
1169a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * <p>
1179a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * The implementer is responsible for obtaining an accessibility node info
1189a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * from the pool of reusable instances and setting the desired properties of
1199a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * the node info before returning it.
1209a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * </p>
1219a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
1229a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param virtualViewId A client defined virtual view id.
1239a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual
1249a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *         descendant or the host View.
1259a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @see AccessibilityNodeInfoCompat
1269a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
1279a81ce92c381007affe6bb2310bf94c9856eaae1alanv    @Override
1289a81ce92c381007affe6bb2310bf94c9856eaae1alanv    public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
1299a81ce92c381007affe6bb2310bf94c9856eaae1alanv        AccessibilityNodeInfoCompat info = null;
1309a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1319a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (virtualViewId == View.NO_ID) {
1329a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // We are requested to create an AccessibilityNodeInfo describing
1339a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // this View, i.e. the root of the virtual sub-tree.
1349a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info = AccessibilityNodeInfoCompat.obtain(mKeyboardView);
1359a81ce92c381007affe6bb2310bf94c9856eaae1alanv            ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, info);
1369a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1379a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // Add the virtual children of the root View.
1389a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // TODO(alanv): Need to assign a unique ID to each key.
1399a81ce92c381007affe6bb2310bf94c9856eaae1alanv            final Keyboard keyboard = mKeyboardView.getKeyboard();
1409a81ce92c381007affe6bb2310bf94c9856eaae1alanv            final Set<Key> keys = keyboard.mKeys;
1419a81ce92c381007affe6bb2310bf94c9856eaae1alanv            for (Key key : keys) {
1429a81ce92c381007affe6bb2310bf94c9856eaae1alanv                final int childVirtualViewId = generateVirtualViewIdForKey(key);
1439a81ce92c381007affe6bb2310bf94c9856eaae1alanv                info.addChild(mKeyboardView, childVirtualViewId);
1449a81ce92c381007affe6bb2310bf94c9856eaae1alanv            }
1459a81ce92c381007affe6bb2310bf94c9856eaae1alanv        } else {
1469a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // Find the view that corresponds to the given id.
1479a81ce92c381007affe6bb2310bf94c9856eaae1alanv            final Key key = mVirtualViewIdToKey.get(virtualViewId);
1489a81ce92c381007affe6bb2310bf94c9856eaae1alanv            if (key == null) {
1499a81ce92c381007affe6bb2310bf94c9856eaae1alanv                Log.e(TAG, "Invalid virtual view ID: " + virtualViewId);
1509a81ce92c381007affe6bb2310bf94c9856eaae1alanv                return null;
1519a81ce92c381007affe6bb2310bf94c9856eaae1alanv            }
1529a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1539a81ce92c381007affe6bb2310bf94c9856eaae1alanv            final String keyDescription = getKeyDescription(key);
1549a81ce92c381007affe6bb2310bf94c9856eaae1alanv            final Rect boundsInParent = key.mHitBox;
1559a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1569a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // Calculate the key's in-screen bounds.
1579a81ce92c381007affe6bb2310bf94c9856eaae1alanv            mTempBoundsInScreen.set(boundsInParent);
1589a81ce92c381007affe6bb2310bf94c9856eaae1alanv            mTempBoundsInScreen.offset(mParentLocation[0], mParentLocation[1]);
1599a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1609a81ce92c381007affe6bb2310bf94c9856eaae1alanv            final Rect boundsInScreen = mTempBoundsInScreen;
1619a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1629a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // Obtain and initialize an AccessibilityNodeInfo with
1639a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // information about the virtual view.
1649a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info = AccessibilityNodeInfoCompat.obtain();
1659a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.addAction(AccessibilityNodeInfoCompat.ACTION_SELECT);
1669a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION);
1679a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.setPackageName(mKeyboardView.getContext().getPackageName());
1689a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.setClassName(key.getClass().getName());
1699a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.setBoundsInParent(boundsInParent);
1709a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.setBoundsInScreen(boundsInScreen);
1719a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.setParent(mKeyboardView);
1729a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.setSource(mKeyboardView, virtualViewId);
1739a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.setBoundsInScreen(boundsInScreen);
1749a81ce92c381007affe6bb2310bf94c9856eaae1alanv            info.setText(keyDescription);
1759a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
1769a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1779a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return info;
1789a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
1799a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1809a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
1819a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Performs an accessibility action on a virtual view, i.e. a descendant of
1829a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * the host View, with the given <code>virtualViewId</code> or the host View itself if
1839a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * <code>virtualViewId</code> equals to {@link View#NO_ID}.
1849a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
1859a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param action The action to perform.
1869a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param virtualViewId A client defined virtual view id.
1879a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return True if the action was performed.
1889a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @see #createAccessibilityNodeInfo(int)
1899a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @see AccessibilityNodeInfoCompat
1909a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
1919a81ce92c381007affe6bb2310bf94c9856eaae1alanv    @Override
1929a81ce92c381007affe6bb2310bf94c9856eaae1alanv    public boolean performAccessibilityAction(int action, int virtualViewId) {
1939a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (virtualViewId == View.NO_ID) {
1949a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // Perform the action on the host View.
1959a81ce92c381007affe6bb2310bf94c9856eaae1alanv            switch (action) {
1969a81ce92c381007affe6bb2310bf94c9856eaae1alanv            case AccessibilityNodeInfoCompat.ACTION_SELECT:
1979a81ce92c381007affe6bb2310bf94c9856eaae1alanv                if (!mKeyboardView.isSelected()) {
1989a81ce92c381007affe6bb2310bf94c9856eaae1alanv                    mKeyboardView.setSelected(true);
1999a81ce92c381007affe6bb2310bf94c9856eaae1alanv                    return mKeyboardView.isSelected();
2009a81ce92c381007affe6bb2310bf94c9856eaae1alanv                }
2019a81ce92c381007affe6bb2310bf94c9856eaae1alanv                break;
2029a81ce92c381007affe6bb2310bf94c9856eaae1alanv            case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION:
2039a81ce92c381007affe6bb2310bf94c9856eaae1alanv                if (mKeyboardView.isSelected()) {
2049a81ce92c381007affe6bb2310bf94c9856eaae1alanv                    mKeyboardView.setSelected(false);
2059a81ce92c381007affe6bb2310bf94c9856eaae1alanv                    return !mKeyboardView.isSelected();
2069a81ce92c381007affe6bb2310bf94c9856eaae1alanv                }
2079a81ce92c381007affe6bb2310bf94c9856eaae1alanv                break;
2089a81ce92c381007affe6bb2310bf94c9856eaae1alanv            }
2099a81ce92c381007affe6bb2310bf94c9856eaae1alanv        } else {
2109a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // Find the view that corresponds to the given id.
2119a81ce92c381007affe6bb2310bf94c9856eaae1alanv            final Key child = mVirtualViewIdToKey.get(virtualViewId);
2129a81ce92c381007affe6bb2310bf94c9856eaae1alanv            if (child == null)
2139a81ce92c381007affe6bb2310bf94c9856eaae1alanv                return false;
2149a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2159a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // Perform the action on a virtual view.
2169a81ce92c381007affe6bb2310bf94c9856eaae1alanv            switch (action) {
2179a81ce92c381007affe6bb2310bf94c9856eaae1alanv            case AccessibilityNodeInfoCompat.ACTION_SELECT:
2189a81ce92c381007affe6bb2310bf94c9856eaae1alanv                // TODO: Provide some focus indicator.
2199a81ce92c381007affe6bb2310bf94c9856eaae1alanv                return true;
2209a81ce92c381007affe6bb2310bf94c9856eaae1alanv            case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION:
2219a81ce92c381007affe6bb2310bf94c9856eaae1alanv                // TODO: Provide some clear focus indicator.
2229a81ce92c381007affe6bb2310bf94c9856eaae1alanv                return true;
2239a81ce92c381007affe6bb2310bf94c9856eaae1alanv            }
2249a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
2259a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2269a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return false;
2279a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
2289a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2299a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
2309a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Finds {@link AccessibilityNodeInfoCompat}s by text. The match is case
2319a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * insensitive containment. The search is relative to the virtual view, i.e.
2329a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * a descendant of the host View, with the given <code>virtualViewId</code> or the host
2339a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * View itself <code>virtualViewId</code> equals to {@link View#NO_ID}.
2349a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
2359a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param virtualViewId A client defined virtual view id which defined the
2369a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *            root of the tree in which to perform the search.
2379a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param text The searched text.
2389a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return A list of node info.
2399a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @see #createAccessibilityNodeInfo(int)
2409a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @see AccessibilityNodeInfoCompat
2419a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
2429a81ce92c381007affe6bb2310bf94c9856eaae1alanv    @Override
2439a81ce92c381007affe6bb2310bf94c9856eaae1alanv    public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText(
2449a81ce92c381007affe6bb2310bf94c9856eaae1alanv            String text, int virtualViewId) {
2459a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final String searchedLowerCase = text.toLowerCase();
2469a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final Keyboard keyboard = mKeyboardView.getKeyboard();
2479a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2489a81ce92c381007affe6bb2310bf94c9856eaae1alanv        List<AccessibilityNodeInfoCompat> results = null;
2499a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2509a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (virtualViewId == View.NO_ID) {
2519a81ce92c381007affe6bb2310bf94c9856eaae1alanv            for (Key key : keyboard.mKeys) {
2529a81ce92c381007affe6bb2310bf94c9856eaae1alanv                results = findByTextAndPopulate(searchedLowerCase, key, results);
2539a81ce92c381007affe6bb2310bf94c9856eaae1alanv            }
2549a81ce92c381007affe6bb2310bf94c9856eaae1alanv        } else {
2559a81ce92c381007affe6bb2310bf94c9856eaae1alanv            final Key key = mVirtualViewIdToKey.get(virtualViewId);
2569a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2579a81ce92c381007affe6bb2310bf94c9856eaae1alanv            results = findByTextAndPopulate(searchedLowerCase, key, results);
2589a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
2599a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2609a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (results == null) {
2619a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return Collections.emptyList();
2629a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
2639a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2649a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return results;
2659a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
2669a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2679a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
2689a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Helper method for {@link #findAccessibilityNodeInfosByText(String, int)}.
2699a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Takes a current set of results and matches a specified key against a
2709a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * lower-case search string. Returns an updated list of results.
2719a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
2729a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param searchedLowerCase The lower-case search string.
2739a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key The key to compare against.
2749a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param results The current list of results, or {@code null} if no results
2759a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *            found.
2769a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return An updated list of results, or {@code null} if no results found.
2779a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
2789a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private List<AccessibilityNodeInfoCompat> findByTextAndPopulate(String searchedLowerCase,
2799a81ce92c381007affe6bb2310bf94c9856eaae1alanv            Key key, List<AccessibilityNodeInfoCompat> results) {
2809a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (!keyContainsText(key, searchedLowerCase)) {
2819a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return results;
2829a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
2839a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2849a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final int childVirtualViewId = generateVirtualViewIdForKey(key);
2859a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final AccessibilityNodeInfoCompat nodeInfo = createAccessibilityNodeInfo(
2869a81ce92c381007affe6bb2310bf94c9856eaae1alanv                childVirtualViewId);
2879a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2889a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (results == null) {
2899a81ce92c381007affe6bb2310bf94c9856eaae1alanv            results = new LinkedList<AccessibilityNodeInfoCompat>();
2909a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
2919a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2929a81ce92c381007affe6bb2310bf94c9856eaae1alanv        results.add(nodeInfo);
2939a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2949a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return results;
2959a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
2969a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2979a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
2989a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Returns whether a key's current description contains the lower-case
2999a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * search text.
3009a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
3019a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key The key to compare against.
3029a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param textLowerCase The lower-case search string.
3039a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return {@code true} if the key contains the search text.
3049a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
3059a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private boolean keyContainsText(Key key, String textLowerCase) {
3069a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (key == null) {
3079a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return false;
3089a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
3099a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3109a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final String description = getKeyDescription(key);
3119a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3129a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (description == null) {
3139a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return false;
3149a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
3159a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3169a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return description.toLowerCase().contains(textLowerCase);
3179a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
3189a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3199a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
3209a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Returns the context-specific description for a {@link Key}.
3219a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
3229a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key The key to describe.
3239a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return The context-specific description of the key.
3249a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
3259a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private String getKeyDescription(Key key) {
3269a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
3279a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo);
3289a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final String keyDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
3299a81ce92c381007affe6bb2310bf94c9856eaae1alanv                mKeyboardView.getContext(), mKeyboardView.getKeyboard(), key, shouldObscure);
3309a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3319a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return keyDescription;
3329a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
3339a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3349a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
3359a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Assigns virtual view IDs to keyboard keys and populates the related maps.
3369a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
3379a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private void assignVirtualViewIds() {
3389a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final Keyboard keyboard = mKeyboardView.getKeyboard();
3399a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (keyboard == null) {
3409a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return;
3419a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
3429a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3439a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mVirtualViewIdToKey.clear();
3449a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3459a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final Set<Key> keySet = keyboard.mKeys;
3469a81ce92c381007affe6bb2310bf94c9856eaae1alanv        for (Key key : keySet) {
3479a81ce92c381007affe6bb2310bf94c9856eaae1alanv            final int virtualViewId = generateVirtualViewIdForKey(key);
3489a81ce92c381007affe6bb2310bf94c9856eaae1alanv            mVirtualViewIdToKey.put(virtualViewId, key);
3499a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
3509a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
3519a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3529a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
3539a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Updates the parent's on-screen location.
3549a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
3559a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private void updateParentLocation() {
3569a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mKeyboardView.getLocationOnScreen(mParentLocation);
3579a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
3589a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3599a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
3609a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Generates a virtual view identifier for the specified key.
3619a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
3629a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key The key to identify.
3639a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return A virtual view identifier.
3649a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
3659a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private static int generateVirtualViewIdForKey(Key key) {
3669a81ce92c381007affe6bb2310bf94c9856eaae1alanv        // The key code is unique within an instance of a Keyboard.
3679a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return key.mCode;
3689a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
3699a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3709a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() {
3719a81ce92c381007affe6bb2310bf94c9856eaae1alanv        @Override
3729a81ce92c381007affe6bb2310bf94c9856eaae1alanv        public void onGlobalLayout() {
3739a81ce92c381007affe6bb2310bf94c9856eaae1alanv            assignVirtualViewIds();
3749a81ce92c381007affe6bb2310bf94c9856eaae1alanv            updateParentLocation();
3759a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
3769a81ce92c381007affe6bb2310bf94c9856eaae1alanv    };
3779a81ce92c381007affe6bb2310bf94c9856eaae1alanv}
378