14d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka/*
24d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka * Copyright (C) 2014 The Android Open Source Project
34d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka *
44d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
54d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka * you may not use this file except in compliance with the License.
64d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka * You may obtain a copy of the License at
74d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka *
84d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
94d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka *
104d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
114d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
124d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka * See the License for the specific language governing permissions and
144d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka * limitations under the License.
154d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka */
164d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
174d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokapackage com.android.inputmethod.accessibility;
184d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
194d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokaimport android.content.Context;
20176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaokaimport android.graphics.Rect;
2162316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaokaimport android.os.SystemClock;
2262316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaokaimport android.util.Log;
234d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokaimport android.util.SparseIntArray;
2462316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaokaimport android.view.MotionEvent;
254d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
2662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaokaimport com.android.inputmethod.keyboard.Key;
274d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokaimport com.android.inputmethod.keyboard.KeyDetector;
284d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokaimport com.android.inputmethod.keyboard.Keyboard;
294d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokaimport com.android.inputmethod.keyboard.KeyboardId;
304d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokaimport com.android.inputmethod.keyboard.MainKeyboardView;
3162316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaokaimport com.android.inputmethod.keyboard.PointerTracker;
324d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokaimport com.android.inputmethod.latin.R;
334d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokaimport com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
344d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
3562316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka/**
3662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka * This class represents a delegate that can be registered in {@link MainKeyboardView} to enhance
3762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka * accessibility support via composition rather via inheritance.
3862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka */
394d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaokapublic final class MainKeyboardAccessibilityDelegate
4062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        extends KeyboardAccessibilityDelegate<MainKeyboardView>
4162316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        implements AccessibilityLongPressTimer.LongPressTimerCallback {
4262316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    private static final String TAG = MainKeyboardAccessibilityDelegate.class.getSimpleName();
4362316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka
444d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    /** Map of keyboard modes to resource IDs. */
454d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray();
464d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
474d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    static {
484d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATE, R.string.keyboard_mode_date);
494d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATETIME, R.string.keyboard_mode_date_time);
504d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_EMAIL, R.string.keyboard_mode_email);
514d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_IM, R.string.keyboard_mode_im);
524d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_NUMBER, R.string.keyboard_mode_number);
534d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_PHONE, R.string.keyboard_mode_phone);
544d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TEXT, R.string.keyboard_mode_text);
554d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TIME, R.string.keyboard_mode_time);
564d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url);
574d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    }
584d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
594d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    /** The most recently set keyboard mode. */
604d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
614d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    private static final int KEYBOARD_IS_HIDDEN = -1;
62176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka    // The rectangle region to ignore hover events.
63176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka    private final Rect mBoundsToIgnoreHoverEvent = new Rect();
6462316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka
6562316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    private final AccessibilityLongPressTimer mAccessibilityLongPressTimer;
664d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
674d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    public MainKeyboardAccessibilityDelegate(final MainKeyboardView mainKeyboardView,
684d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            final KeyDetector keyDetector) {
694d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        super(mainKeyboardView, keyDetector);
7062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        mAccessibilityLongPressTimer = new AccessibilityLongPressTimer(
7162316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka                this /* callback */, mainKeyboardView.getContext());
724d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    }
734d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
744d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    /**
754d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * {@inheritDoc}
764d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     */
774d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    @Override
784d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    public void setKeyboard(final Keyboard keyboard) {
794d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        if (keyboard == null) {
804d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            return;
814d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        }
824d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        final Keyboard lastKeyboard = getKeyboard();
834d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        super.setKeyboard(keyboard);
844d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        final int lastKeyboardMode = mLastKeyboardMode;
854d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        mLastKeyboardMode = keyboard.mId.mMode;
864d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
874d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        // Since this method is called even when accessibility is off, make sure
884d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        // to check the state before announcing anything.
894d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        if (!AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
904d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            return;
914d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        }
924d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        // Announce the language name only when the language is changed.
934d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        if (lastKeyboard == null || !keyboard.mId.mSubtype.equals(lastKeyboard.mId.mSubtype)) {
944d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            announceKeyboardLanguage(keyboard);
954d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            return;
964d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        }
974d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        // Announce the mode only when the mode is changed.
984d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        if (keyboard.mId.mMode != lastKeyboardMode) {
994d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            announceKeyboardMode(keyboard);
1004d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            return;
1014d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        }
1024d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        // Announce the keyboard type only when the type is changed.
1034d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        if (keyboard.mId.mElementId != lastKeyboard.mId.mElementId) {
1044d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            announceKeyboardType(keyboard, lastKeyboard);
1054d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            return;
1064d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        }
1074d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    }
1084d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
1094d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    /**
1104d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * Called when the keyboard is hidden and accessibility is enabled.
1114d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     */
1124d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    public void onHideWindow() {
11338b15afb47feab79c8a8bc90e3daf2afcba935acYohei Yukawa        if (mLastKeyboardMode != KEYBOARD_IS_HIDDEN) {
11438b15afb47feab79c8a8bc90e3daf2afcba935acYohei Yukawa            announceKeyboardHidden();
11538b15afb47feab79c8a8bc90e3daf2afcba935acYohei Yukawa        }
1164d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
1174d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    }
1184d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
1194d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    /**
1204d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * Announces which language of keyboard is being displayed.
1214d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     *
1224d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * @param keyboard The new keyboard.
1234d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     */
1244d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    private void announceKeyboardLanguage(final Keyboard keyboard) {
1254d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        final String languageText = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(
12685ddfe1317a4475269e53f62c2338c335e02e839Jean Chalard                keyboard.mId.mSubtype.getRawSubtype());
1274d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        sendWindowStateChanged(languageText);
1284d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    }
1294d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
1304d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    /**
1314d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * Announces which type of keyboard is being displayed.
1324d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * If the keyboard type is unknown, no announcement is made.
1334d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     *
1344d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * @param keyboard The new keyboard.
1354d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     */
1364d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    private void announceKeyboardMode(final Keyboard keyboard) {
1374d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        final Context context = mKeyboardView.getContext();
1384d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(keyboard.mId.mMode);
1394d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        if (modeTextResId == 0) {
1404d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            return;
1414d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        }
1424d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        final String modeText = context.getString(modeTextResId);
1434d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        final String text = context.getString(R.string.announce_keyboard_mode, modeText);
1444d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        sendWindowStateChanged(text);
1454d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    }
1464d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
1474d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    /**
1484d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * Announces which type of keyboard is being displayed.
1494d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     *
1504d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * @param keyboard The new keyboard.
1514d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * @param lastKeyboard The last keyboard.
1524d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     */
1534d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    private void announceKeyboardType(final Keyboard keyboard, final Keyboard lastKeyboard) {
1544d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        final int lastElementId = lastKeyboard.mId.mElementId;
1554d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        final int resId;
1564d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        switch (keyboard.mId.mElementId) {
1574d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
1584d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        case KeyboardId.ELEMENT_ALPHABET:
1594d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            if (lastElementId == KeyboardId.ELEMENT_ALPHABET
1604d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka                    || lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
16148b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka                // Transition between alphabet mode and automatic shifted mode should be silently
16248b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka                // ignored because it can be determined by each key's talk back announce.
1634d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka                return;
1644d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            }
1654d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            resId = R.string.spoken_description_mode_alpha;
1664d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            break;
1674d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
16848b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka            if (lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
16948b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka                // Resetting automatic shifted mode by pressing the shift key causes the transition
17048b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka                // from automatic shifted to manual shifted that should be silently ignored.
17148b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka                return;
17248b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka            }
1734d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            resId = R.string.spoken_description_shiftmode_on;
1744d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            break;
1754d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
17648b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka            if (lastElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) {
17748b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka                // Resetting caps locked mode by pressing the shift key causes the transition
17848b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka                // from shift locked to shift lock shifted that should be silently ignored.
17948b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka                return;
18048b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka            }
18148b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka            resId = R.string.spoken_description_shiftmode_locked;
18248b96a6a52dab6f3a44adf80b40832d629fe5871Tadashi G. Takaoka            break;
1834d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
1844d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            resId = R.string.spoken_description_shiftmode_locked;
1854d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            break;
1864d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        case KeyboardId.ELEMENT_SYMBOLS:
1874d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            resId = R.string.spoken_description_mode_symbol;
1884d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            break;
1894d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
1904d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            resId = R.string.spoken_description_mode_symbol_shift;
1914d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            break;
1924d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        case KeyboardId.ELEMENT_PHONE:
1934d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            resId = R.string.spoken_description_mode_phone;
1944d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            break;
1954d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
1964d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            resId = R.string.spoken_description_mode_phone_shift;
1974d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            break;
1984d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        default:
1994d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka            return;
2004d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        }
20187d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka        sendWindowStateChanged(resId);
2024d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    }
2034d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka
2044d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    /**
2054d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * Announces that the keyboard has been hidden.
2064d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     */
2074d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    private void announceKeyboardHidden() {
20887d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka        sendWindowStateChanged(R.string.announce_keyboard_hidden);
2094d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    }
21062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka
21162316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    @Override
2121e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    public void performClickOn(final Key key) {
213176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        final int x = key.getHitBox().centerX();
214176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        final int y = key.getHitBox().centerY();
21562316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (DEBUG_HOVER) {
2161e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka            Log.d(TAG, "performClickOn: key=" + key
217176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka                    + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y));
21862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
219176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        if (mBoundsToIgnoreHoverEvent.contains(x, y)) {
220176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            // This hover exit event points to the key that should be ignored.
221176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            // Clear the ignoring region to handle further hover events.
222176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            mBoundsToIgnoreHoverEvent.setEmpty();
223176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            return;
22462316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
2251e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        super.performClickOn(key);
22662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    }
22762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka
22862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    @Override
22962316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    protected void onHoverEnterTo(final Key key) {
230176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        final int x = key.getHitBox().centerX();
231176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        final int y = key.getHitBox().centerY();
23262316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (DEBUG_HOVER) {
233176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            Log.d(TAG, "onHoverEnterTo: key=" + key
234176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka                    + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y));
23562316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
23662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        mAccessibilityLongPressTimer.cancelLongPress();
237176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        if (mBoundsToIgnoreHoverEvent.contains(x, y)) {
238176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            return;
239176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        }
240176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        // This hover enter event points to the key that isn't in the ignoring region.
241176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        // Further hover events should be handled.
242176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        mBoundsToIgnoreHoverEvent.setEmpty();
24362316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        super.onHoverEnterTo(key);
24462316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (key.isLongPressEnabled()) {
24562316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka            mAccessibilityLongPressTimer.startLongPress(key);
24662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
24762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    }
24862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka
249176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka    @Override
25062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    protected void onHoverExitFrom(final Key key) {
251176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        final int x = key.getHitBox().centerX();
252176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        final int y = key.getHitBox().centerY();
25362316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (DEBUG_HOVER) {
254176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            Log.d(TAG, "onHoverExitFrom: key=" + key
255176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka                    + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y));
25662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
25762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        mAccessibilityLongPressTimer.cancelLongPress();
25862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        super.onHoverExitFrom(key);
25962316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    }
26062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka
26162316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    @Override
2621e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    public void performLongClickOn(final Key key) {
26362316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (DEBUG_HOVER) {
2641e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka            Log.d(TAG, "performLongClickOn: key=" + key);
26562316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
26662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        final PointerTracker tracker = PointerTracker.getPointerTracker(HOVER_EVENT_POINTER_ID);
26762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        final long eventTime = SystemClock.uptimeMillis();
26862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        final int x = key.getHitBox().centerX();
26962316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        final int y = key.getHitBox().centerY();
27062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        final MotionEvent downEvent = MotionEvent.obtain(
27162316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka                eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */);
27262316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        // Inject a fake down event to {@link PointerTracker} to handle a long press correctly.
27362316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        tracker.processMotionEvent(downEvent, mKeyDetector);
27462316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        downEvent.recycle();
27553b6d627e7fe66ce47ee6ae01254abc070558e77Tadashi G. Takaoka        // Invoke {@link PointerTracker#onLongPressed()} as if a long press timeout has passed.
27653b6d627e7fe66ce47ee6ae01254abc070558e77Tadashi G. Takaoka        tracker.onLongPressed();
27762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        // If {@link Key#hasNoPanelAutoMoreKeys()} is true (such as "0 +" key on the phone layout)
27862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        // or a key invokes IME switcher dialog, we should just ignore the next
27962316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        // {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether
28062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        // {@link PointerTracker} is in operation or not.
281176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        if (tracker.isInOperation()) {
282176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            // This long press shows a more keys keyboard and further hover events should be
283176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            // handled.
284176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            mBoundsToIgnoreHoverEvent.setEmpty();
285176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            return;
286176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        }
287176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        // This long press has handled at {@link MainKeyboardView#onLongPress(PointerTracker)}.
288176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        // We should ignore further hover events on this key.
289176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        mBoundsToIgnoreHoverEvent.set(key.getHitBox());
290176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        if (key.hasNoPanelAutoMoreKey()) {
291176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            // This long press has registered a code point without showing a more keys keyboard.
292176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            // We should talk back the code point if possible.
293176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            final int codePointOfNoPanelAutoMoreKey = key.getMoreKeys()[0].mCode;
294176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            final String text = KeyCodeDescriptionMapper.getInstance().getDescriptionForCodePoint(
295176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka                    mKeyboardView.getContext(), codePointOfNoPanelAutoMoreKey);
296176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            if (text != null) {
297176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka                sendWindowStateChanged(text);
298176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka            }
299176f803176de964cbb3715cfe033797de62aa1feTadashi G. Takaoka        }
30062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    }
3014d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka}
302