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;
20f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanvimport android.os.Bundle;
219a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.ViewCompat;
22f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanvimport android.support.v4.view.accessibility.AccessibilityEventCompat;
239a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
249a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
259a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.accessibility.AccessibilityRecordCompat;
269a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.util.Log;
279a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.View;
289a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.accessibility.AccessibilityEvent;
299a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.inputmethod.EditorInfo;
309a81ce92c381007affe6bb2310bf94c9856eaae1alanv
319a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport com.android.inputmethod.keyboard.Key;
329a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport com.android.inputmethod.keyboard.Keyboard;
339a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport com.android.inputmethod.keyboard.KeyboardView;
345b91b551e5ffaf2c2e691dfbd434f21c82293986Jean Chalardimport com.android.inputmethod.latin.common.CoordinateUtils;
3567319f92f31ca5b40e1f80f7b9ae63b9d8886f0eAlan Viveretteimport com.android.inputmethod.latin.settings.Settings;
3667319f92f31ca5b40e1f80f7b9ae63b9d8886f0eAlan Viveretteimport com.android.inputmethod.latin.settings.SettingsValues;
379a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3892892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaokaimport java.util.List;
3992892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka
409a81ce92c381007affe6bb2310bf94c9856eaae1alanv/**
419a81ce92c381007affe6bb2310bf94c9856eaae1alanv * Exposes a virtual view sub-tree for {@link KeyboardView} and generates
429a81ce92c381007affe6bb2310bf94c9856eaae1alanv * {@link AccessibilityEvent}s for individual {@link Key}s.
439a81ce92c381007affe6bb2310bf94c9856eaae1alanv * <p>
449a81ce92c381007affe6bb2310bf94c9856eaae1alanv * A virtual sub-tree is composed of imaginary {@link View}s that are reported
459a81ce92c381007affe6bb2310bf94c9856eaae1alanv * as a part of the view hierarchy for accessibility purposes. This enables
469a81ce92c381007affe6bb2310bf94c9856eaae1alanv * custom views that draw complex content to report them selves as a tree of
479a81ce92c381007affe6bb2310bf94c9856eaae1alanv * virtual views, thus conveying their logical structure.
489a81ce92c381007affe6bb2310bf94c9856eaae1alanv * </p>
499a81ce92c381007affe6bb2310bf94c9856eaae1alanv */
501e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaokafinal class KeyboardAccessibilityNodeProvider<KV extends KeyboardView>
511e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        extends AccessibilityNodeProviderCompat {
527b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaoka    private static final String TAG = KeyboardAccessibilityNodeProvider.class.getSimpleName();
539934740a5fd85ad0ea6126328ba9015069d2fdc0Tadashi G. Takaoka
549934740a5fd85ad0ea6126328ba9015069d2fdc0Tadashi G. Takaoka    // From {@link android.view.accessibility.AccessibilityNodeInfo#UNDEFINED_ITEM_ID}.
559934740a5fd85ad0ea6126328ba9015069d2fdc0Tadashi G. Takaoka    private static final int UNDEFINED = Integer.MAX_VALUE;
569a81ce92c381007affe6bb2310bf94c9856eaae1alanv
579a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper;
589a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final AccessibilityUtils mAccessibilityUtils;
599a81ce92c381007affe6bb2310bf94c9856eaae1alanv
609a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /** Temporary rect used to calculate in-screen bounds. */
619a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private final Rect mTempBoundsInScreen = new Rect();
629a81ce92c381007affe6bb2310bf94c9856eaae1alanv
639a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /** The parent view's cached on-screen location. */
64359c35e0f6e534d096efc1d9f2ff585139e3b3acTadashi G. Takaoka    private final int[] mParentLocation = CoordinateUtils.newInstance();
659a81ce92c381007affe6bb2310bf94c9856eaae1alanv
66f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    /** The virtual view identifier for the focused node. */
67f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    private int mAccessibilityFocusedView = UNDEFINED;
68f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
69d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka    /** The virtual view identifier for the hovering node. */
70d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka    private int mHoveringNodeId = UNDEFINED;
71d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka
723d8848e5cb709fb47b450e7ede5a2926d99c957dTadashi G. Takaoka    /** The keyboard view to provide an accessibility node info. */
731e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    private final KV mKeyboardView;
741e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    /** The accessibility delegate. */
751e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    private final KeyboardAccessibilityDelegate<KV> mDelegate;
7648ccd5528163383a46b597e9d5ea919ddc799f25alanv
7792892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka    /** The current keyboard. */
7892892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka    private Keyboard mKeyboard;
7992892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka
801e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    public KeyboardAccessibilityNodeProvider(final KV keyboardView,
811e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka            final KeyboardAccessibilityDelegate<KV> delegate) {
827b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaoka        super();
839a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance();
849a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mAccessibilityUtils = AccessibilityUtils.getInstance();
8548ccd5528163383a46b597e9d5ea919ddc799f25alanv        mKeyboardView = keyboardView;
861e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        mDelegate = delegate;
87f147794fd41491a3383e6aca6d49007f58124068alanv
88f147794fd41491a3383e6aca6d49007f58124068alanv        // Since this class is constructed lazily, we might not get a subsequent
89f147794fd41491a3383e6aca6d49007f58124068alanv        // call to setKeyboard() and therefore need to call it now.
9092892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        setKeyboard(keyboardView.getKeyboard());
91f147794fd41491a3383e6aca6d49007f58124068alanv    }
9248ccd5528163383a46b597e9d5ea919ddc799f25alanv
93f147794fd41491a3383e6aca6d49007f58124068alanv    /**
94f147794fd41491a3383e6aca6d49007f58124068alanv     * Sets the keyboard represented by this node provider.
9592892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka     *
9692892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka     * @param keyboard The keyboard that is being set to the keyboard view.
97f147794fd41491a3383e6aca6d49007f58124068alanv     */
9892892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka    public void setKeyboard(final Keyboard keyboard) {
9992892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        mKeyboard = keyboard;
10092892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka    }
10192892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka
10292892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka    private Key getKeyOf(final int virtualViewId) {
10392892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        if (mKeyboard == null) {
10492892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka            return null;
10592892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        }
10692892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        final List<Key> sortedKeys = mKeyboard.getSortedKeys();
10792892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        // Use a virtual view id as an index of the sorted keys list.
10892892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        if (virtualViewId >= 0 && virtualViewId < sortedKeys.size()) {
10992892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka            return sortedKeys.get(virtualViewId);
11092892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        }
11192892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        return null;
11292892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka    }
11392892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka
11492892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka    private int getVirtualViewIdOf(final Key key) {
11592892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        if (mKeyboard == null) {
11692892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka            return View.NO_ID;
11792892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        }
11892892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        final List<Key> sortedKeys = mKeyboard.getSortedKeys();
11992892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        final int size = sortedKeys.size();
12092892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        for (int index = 0; index < size; index++) {
12192892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka            if (sortedKeys.get(index) == key) {
12292892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka                // Use an index of the sorted keys list as a virtual view id.
12392892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka                return index;
12492892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka            }
12592892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        }
12692892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        return View.NO_ID;
1279a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
1289a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1299a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
1309a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Creates and populates an {@link AccessibilityEvent} for the specified key
1319a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * and event type.
1329a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
1339a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key A key on the host keyboard view.
1349a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param eventType The event type to create.
1359a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return A populated {@link AccessibilityEvent} for the key.
1369a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @see AccessibilityEvent
1379a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
138b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    public AccessibilityEvent createAccessibilityEvent(final Key key, final int eventType) {
13992892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        final int virtualViewId = getVirtualViewIdOf(key);
1409a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final String keyDescription = getKeyDescription(key);
1419a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
1429a81ce92c381007affe6bb2310bf94c9856eaae1alanv        event.setPackageName(mKeyboardView.getContext().getPackageName());
1439a81ce92c381007affe6bb2310bf94c9856eaae1alanv        event.setClassName(key.getClass().getName());
1446662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv        event.setContentDescription(keyDescription);
14526c80a1b9a103cdccbaeafac75a3db2543a9ee7ealanv        event.setEnabled(true);
146a80d55d4d374e6ca4459f975e885c2d4649c5f96Tadashi G. Takaoka        final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
1479a81ce92c381007affe6bb2310bf94c9856eaae1alanv        record.setSource(mKeyboardView, virtualViewId);
1489a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return event;
1499a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
1509a81ce92c381007affe6bb2310bf94c9856eaae1alanv
151d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka    public void onHoverEnterTo(final Key key) {
152d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        final int id = getVirtualViewIdOf(key);
153d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        if (id == View.NO_ID) {
154d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka            return;
155d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        }
156d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        // Start hovering on the key. Because our accessibility model is lift-to-type, we should
157d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        // report the node info without click and long click actions to avoid unnecessary
158d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        // announcements.
159d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        mHoveringNodeId = id;
160d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        // Invalidate the node info of the key.
161d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
162d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
163d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka    }
164d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka
165d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka    public void onHoverExitFrom(final Key key) {
166d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        mHoveringNodeId = UNDEFINED;
167d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        // Invalidate the node info of the key to be able to revert the change we have done
168d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        // in {@link #onHoverEnterTo(Key)}.
169d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
170d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
171d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka    }
172d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka
1739a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
1749a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual
1759a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or
1769a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}.
1779a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * <p>
1789a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * A virtual descendant is an imaginary View that is reported as a part of
1799a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * the view hierarchy for accessibility purposes. This enables custom views
1809a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * that draw complex content to report them selves as a tree of virtual
1819a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * views, thus conveying their logical structure.
1829a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * </p>
1839a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * <p>
1849a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * The implementer is responsible for obtaining an accessibility node info
1859a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * from the pool of reusable instances and setting the desired properties of
1869a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * the node info before returning it.
1879a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * </p>
1889a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
1899a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param virtualViewId A client defined virtual view id.
190b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual descendant or the host
191b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * View.
1929a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @see AccessibilityNodeInfoCompat
1939a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
1949a81ce92c381007affe6bb2310bf94c9856eaae1alanv    @Override
195b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(final int virtualViewId) {
196f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        if (virtualViewId == UNDEFINED) {
197f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            return null;
198b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        }
199b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        if (virtualViewId == View.NO_ID) {
2009a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // We are requested to create an AccessibilityNodeInfo describing
201d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka            // this View, i.e. the root of the virtual sub-tree.
202b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            final AccessibilityNodeInfoCompat rootInfo =
203b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa                    AccessibilityNodeInfoCompat.obtain(mKeyboardView);
204b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, rootInfo);
205d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka            updateParentLocation();
206d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka
207d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka            // Add the virtual children of the root View.
208d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka            final List<Key> sortedKeys = mKeyboard.getSortedKeys();
209d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka            final int size = sortedKeys.size();
210d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka            for (int index = 0; index < size; index++) {
211d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka                final Key key = sortedKeys.get(index);
212d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka                if (key.isSpacer()) {
213d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka                    continue;
214d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka                }
215d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka                // Use an index of the sorted keys list as a virtual view id.
216d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka                rootInfo.addChild(mKeyboardView, index);
217d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka            }
218b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            return rootInfo;
2199a81ce92c381007affe6bb2310bf94c9856eaae1alanv        }
2209a81ce92c381007affe6bb2310bf94c9856eaae1alanv
22192892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        // Find the key that corresponds to the given virtual view id.
22292892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        final Key key = getKeyOf(virtualViewId);
223b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        if (key == null) {
224b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            Log.e(TAG, "Invalid virtual view ID: " + virtualViewId);
225b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            return null;
226b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        }
227b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        final String keyDescription = getKeyDescription(key);
2287dc60f9db729e93cb591492574a436418c553ebfTadashi G. Takaoka        final Rect boundsInParent = key.getHitBox();
229b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa
230b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        // Calculate the key's in-screen bounds.
231b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        mTempBoundsInScreen.set(boundsInParent);
232b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        mTempBoundsInScreen.offset(
233b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa                CoordinateUtils.x(mParentLocation), CoordinateUtils.y(mParentLocation));
234b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        final Rect boundsInScreen = mTempBoundsInScreen;
235b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa
236b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        // Obtain and initialize an AccessibilityNodeInfo with information about the virtual view.
237b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
238b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        info.setPackageName(mKeyboardView.getContext().getPackageName());
239b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        info.setClassName(key.getClass().getName());
240b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        info.setContentDescription(keyDescription);
241b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        info.setBoundsInParent(boundsInParent);
242b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        info.setBoundsInScreen(boundsInScreen);
243b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        info.setParent(mKeyboardView);
244b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        info.setSource(mKeyboardView, virtualViewId);
245d801b6ee668f6ed4ef1926fd2cdb928fd36ace3aTadashi G. Takaoka        info.setEnabled(key.isEnabled());
246b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        info.setVisibleToUser(true);
247d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        // Don't add ACTION_CLICK and ACTION_LONG_CLOCK actions while hovering on the key.
248d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        // See {@link #onHoverEnterTo(Key)} and {@link #onHoverExitFrom(Key)}.
249d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        if (virtualViewId != mHoveringNodeId) {
250d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka            info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
251d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka            if (key.isLongPressEnabled()) {
252d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka                info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
253d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka            }
254d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        }
255b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa
256b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        if (mAccessibilityFocusedView == virtualViewId) {
257b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
258b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        } else {
259b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            info.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
260b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        }
2619a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return info;
2629a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
2639a81ce92c381007affe6bb2310bf94c9856eaae1alanv
264f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    @Override
265b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    public boolean performAction(final int virtualViewId, final int action,
266b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            final Bundle arguments) {
26792892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        final Key key = getKeyOf(virtualViewId);
268f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        if (key == null) {
269f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            return false;
270f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        }
271a1b47eb44690ecfdc8411d5ada77233dbe8fb2e1Tadashi G. Takaoka        return performActionForKey(key, action);
272f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    }
273f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
274f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    /**
275f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     * Performs the specified accessibility action for the given key.
276f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     *
277f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     * @param key The on which to perform the action.
278f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     * @param action The action to perform.
279b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * @return The result of performing the action, or false if the action is not supported.
280f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     */
281a1b47eb44690ecfdc8411d5ada77233dbe8fb2e1Tadashi G. Takaoka    boolean performActionForKey(final Key key, final int action) {
282f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        switch (action) {
283f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
284a1b47eb44690ecfdc8411d5ada77233dbe8fb2e1Tadashi G. Takaoka            mAccessibilityFocusedView = getVirtualViewIdOf(key);
285f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            sendAccessibilityEventForKey(
286f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
287f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            return true;
288f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
289f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            mAccessibilityFocusedView = UNDEFINED;
290f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            sendAccessibilityEventForKey(
291f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
292f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            return true;
293d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        case AccessibilityNodeInfoCompat.ACTION_CLICK:
294d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka            sendAccessibilityEventForKey(key, AccessibilityEvent.TYPE_VIEW_CLICKED);
2951e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka            mDelegate.performClickOn(key);
296d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka            return true;
297d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK:
298d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka            sendAccessibilityEventForKey(key, AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
2991e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka            mDelegate.performLongClickOn(key);
300d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka            return true;
301b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        default:
302b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            return false;
303f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        }
304f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    }
305f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
306f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    /**
307f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     * Sends an accessibility event for the given {@link Key}.
308f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     *
309f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     * @param key The key that's sending the event.
310f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     * @param eventType The type of event to send.
311f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     */
312b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    void sendAccessibilityEventForKey(final Key key, final int eventType) {
313f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        final AccessibilityEvent event = createAccessibilityEvent(key, eventType);
314067689c57d353e4e37e0457989c6c2686977df9ealanv        mAccessibilityUtils.requestSendAccessibilityEvent(event);
315f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    }
316f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
317f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    /**
3189a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Returns the context-specific description for a {@link Key}.
3199a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
3209a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key The key to describe.
3219a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return The context-specific description of the key.
3229a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
323b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    private String getKeyDescription(final Key key) {
3247a78127a56bc427fbc690cb0561c415a81064e64Tadashi G. Takaoka        final EditorInfo editorInfo = mKeyboard.mId.mEditorInfo;
3259a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo);
32667319f92f31ca5b40e1f80f7b9ae63b9d8886f0eAlan Viverette        final SettingsValues currentSettings = Settings.getInstance().getCurrent();
32767319f92f31ca5b40e1f80f7b9ae63b9d8886f0eAlan Viverette        final String keyCodeDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
32892892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka                mKeyboardView.getContext(), mKeyboard, key, shouldObscure);
32967319f92f31ca5b40e1f80f7b9ae63b9d8886f0eAlan Viverette        if (currentSettings.isWordSeparator(key.getCode())) {
33067319f92f31ca5b40e1f80f7b9ae63b9d8886f0eAlan Viverette            return mAccessibilityUtils.getAutoCorrectionDescription(
33167319f92f31ca5b40e1f80f7b9ae63b9d8886f0eAlan Viverette                    keyCodeDescription, shouldObscure);
33267319f92f31ca5b40e1f80f7b9ae63b9d8886f0eAlan Viverette        }
3335f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka        return keyCodeDescription;
3349a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
3359a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3369a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
3379a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Updates the parent's on-screen location.
3389a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
3399a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private void updateParentLocation() {
3409a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mKeyboardView.getLocationOnScreen(mParentLocation);
3419a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
3429a81ce92c381007affe6bb2310bf94c9856eaae1alanv}
343