15ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette/*
25ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Copyright (C) 2011 The Android Open Source Project
35ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License.
68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at
75ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
95ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Unless required by applicable law or agreed to in writing, software
118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and
148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under the License.
155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */
165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
175ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepackage com.android.inputmethod.accessibility;
185ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
195ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.content.Context;
2058e3f1065ef47e7116299b9d5087ba2a2b6065a2Alan Viveretteimport android.inputmethodservice.InputMethodService;
219a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.AccessibilityDelegateCompat;
229a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.ViewCompat;
239a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.accessibility.AccessibilityEventCompat;
24f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanvimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
25c2ee72a214fef46bc02ce486220365bbefd78714Alan Viveretteimport android.util.SparseIntArray;
265ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.MotionEvent;
279a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.View;
28c2ee72a214fef46bc02ce486220365bbefd78714Alan Viveretteimport android.view.ViewParent;
29c2ee72a214fef46bc02ce486220365bbefd78714Alan Viveretteimport android.view.accessibility.AccessibilityEvent;
305ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
31e7759091ddb5ec18268945d70d9212195bf6497bTadashi G. Takaokaimport com.android.inputmethod.keyboard.Key;
328d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanvimport com.android.inputmethod.keyboard.Keyboard;
338d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanvimport com.android.inputmethod.keyboard.KeyboardId;
34c8e45ddb032554f4e9d4411d8ef47d98db62d77bTadashi G. Takaokaimport com.android.inputmethod.keyboard.MainKeyboardView;
355ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.keyboard.PointerTracker;
368d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanvimport com.android.inputmethod.latin.R;
375ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
38a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
41c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    /** Map of keyboard modes to resource IDs. */
42c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray();
43c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
44c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    static {
45c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATE, R.string.keyboard_mode_date);
46c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATETIME, R.string.keyboard_mode_date_time);
47c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_EMAIL, R.string.keyboard_mode_email);
48c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_IM, R.string.keyboard_mode_im);
49c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_NUMBER, R.string.keyboard_mode_number);
50c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_PHONE, R.string.keyboard_mode_phone);
51c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TEXT, R.string.keyboard_mode_text);
52c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TIME, R.string.keyboard_mode_time);
53c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url);
54c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    }
55c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
5658e3f1065ef47e7116299b9d5087ba2a2b6065a2Alan Viverette    private InputMethodService mInputMethod;
57c8e45ddb032554f4e9d4411d8ef47d98db62d77bTadashi G. Takaoka    private MainKeyboardView mView;
589a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private AccessibilityEntityProvider mAccessibilityNodeProvider;
595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
60e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka    private Key mLastHoverKey = null;
615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
626662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    /**
63b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * Inset in pixels to look for keys when the user's finger exits the keyboard area.
646662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     */
656662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    private int mEdgeSlop;
666662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv
67a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette    /** The most recently set keyboard mode. */
68a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette    private int mLastKeyboardMode;
69a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette
70b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    public static void init(final InputMethodService inputMethod) {
712ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka        sInstance.initInternal(inputMethod);
725ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
735ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
745ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public static AccessibleKeyboardViewProxy getInstance() {
755ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return sInstance;
765ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
775ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
785ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private AccessibleKeyboardViewProxy() {
795ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // Not publicly instantiable.
805ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
815ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
82b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    private void initInternal(final InputMethodService inputMethod) {
8358e3f1065ef47e7116299b9d5087ba2a2b6065a2Alan Viverette        mInputMethod = inputMethod;
84e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
85e76a9b36cabc3eb9222be245e2cf736169432cd6alanv                R.dimen.accessibility_edge_slop);
865ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
875ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
889a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
899a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Sets the view wrapped by this proxy.
909a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
919a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param view The view to wrap.
929a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
93b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    public void setView(final MainKeyboardView view) {
949a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (view == null) {
959a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // Ignore null views.
969a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return;
975ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
989a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mView = view;
995ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1009a81ce92c381007affe6bb2310bf94c9856eaae1alanv        // Ensure that the view has an accessibility delegate.
1019a81ce92c381007affe6bb2310bf94c9856eaae1alanv        ViewCompat.setAccessibilityDelegate(view, this);
10248ccd5528163383a46b597e9d5ea919ddc799f25alanv
103b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        if (mAccessibilityNodeProvider == null) {
104b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            return;
10548ccd5528163383a46b597e9d5ea919ddc799f25alanv        }
106b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa        mAccessibilityNodeProvider.setView(view);
1079a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
1085ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
109c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    /**
110c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * Called when the keyboard layout changes.
111c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * <p>
112c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * <b>Note:</b> This method will be called even if accessibility is not
113c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * enabled.
114c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     */
115b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    public void setKeyboard() {
116559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        if (mView == null) {
117559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette            return;
118559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        }
119c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        if (mAccessibilityNodeProvider != null) {
120c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette            mAccessibilityNodeProvider.setKeyboard();
121c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        }
122a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette        final int keyboardMode = mView.getKeyboard().mId.mMode;
123c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
124c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        // Since this method is called even when accessibility is off, make sure
125a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette        // to check the state before announcing anything. Also, don't announce
126a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette        // changes within the same mode.
127a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()
128a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette                && (mLastKeyboardMode != keyboardMode)) {
129a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette            announceKeyboardMode(keyboardMode);
130c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        }
131a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette        mLastKeyboardMode = keyboardMode;
132c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    }
133c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
134c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    /**
135c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * Called when the keyboard is hidden and accessibility is enabled.
136c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     */
137c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    public void onHideWindow() {
138559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        if (mView == null) {
139559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette            return;
140559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        }
141c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        announceKeyboardHidden();
142a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette        mLastKeyboardMode = -1;
143c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    }
144c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
145c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    /**
146c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * Announces which type of keyboard is being displayed. If the keyboard type
147c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * is unknown, no announcement is made.
148a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette     *
149a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette     * @param mode The new keyboard mode.
150c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     */
151a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette    private void announceKeyboardMode(int mode) {
152a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette        final int resId = KEYBOARD_MODE_RES_IDS.get(mode);
153c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        if (resId == 0) {
154b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            return;
155f147794fd41491a3383e6aca6d49007f58124068alanv        }
156c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        final Context context = mView.getContext();
157c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        final String keyboardMode = context.getString(resId);
158c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        final String text = context.getString(R.string.announce_keyboard_mode, keyboardMode);
159c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        sendWindowStateChanged(text);
160c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    }
161c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
162c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    /**
163c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * Announces that the keyboard has been hidden.
164c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     */
165c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    private void announceKeyboardHidden() {
166c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        final Context context = mView.getContext();
167c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        final String text = context.getString(R.string.announce_keyboard_hidden);
168c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
169c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        sendWindowStateChanged(text);
170c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    }
171c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
172c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    /**
173c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * Sends a window state change event with the specified text.
174c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     *
175a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette     * @param text The text to send with the event.
176c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     */
177c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    private void sendWindowStateChanged(final String text) {
178c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        final AccessibilityEvent stateChange = AccessibilityEvent.obtain(
179c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
180c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        mView.onInitializeAccessibilityEvent(stateChange);
181c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        stateChange.getText().add(text);
182c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        stateChange.setContentDescription(null);
183c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
184c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        final ViewParent parent = mView.getParent();
185c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        if (parent != null) {
186c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette            parent.requestSendAccessibilityEvent(mView, stateChange);
187c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        }
188f147794fd41491a3383e6aca6d49007f58124068alanv    }
189f147794fd41491a3383e6aca6d49007f58124068alanv
1909a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
191b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * Proxy method for View.getAccessibilityNodeProvider(). This method is called in SDK
192b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * version 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) and higher to obtain the virtual
193b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * node hierarchy provider.
1949a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
195559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette     * @param host The host view for the provider.
1969a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return The accessibility node provider for the current keyboard.
1979a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
1989a81ce92c381007affe6bb2310bf94c9856eaae1alanv    @Override
199b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    public AccessibilityEntityProvider getAccessibilityNodeProvider(final View host) {
200559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        if (mView == null) {
201559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette            return null;
202559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        }
203f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        return getAccessibilityNodeProvider();
2045ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
2055ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
2065ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    /**
207b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * Intercepts touch events before dispatch when touch exploration is turned on in ICS and
208b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * higher.
20926b424b6448fbaddc86d11377ca44ff3169a5d7ealanv     *
210dc2ee7772402633817702e95c2a5b17f6dec03ebalanv     * @param event The motion event being dispatched.
21126b424b6448fbaddc86d11377ca44ff3169a5d7ealanv     * @return {@code true} if the event is handled
21226b424b6448fbaddc86d11377ca44ff3169a5d7ealanv     */
213b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    public boolean dispatchTouchEvent(final MotionEvent event) {
21426b424b6448fbaddc86d11377ca44ff3169a5d7ealanv        // To avoid accidental key presses during touch exploration, always drop
215dc2ee7772402633817702e95c2a5b17f6dec03ebalanv        // touch events generated by the user.
21626b424b6448fbaddc86d11377ca44ff3169a5d7ealanv        return false;
21726b424b6448fbaddc86d11377ca44ff3169a5d7ealanv    }
21826b424b6448fbaddc86d11377ca44ff3169a5d7ealanv
21926b424b6448fbaddc86d11377ca44ff3169a5d7ealanv    /**
220b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * Receives hover events when touch exploration is turned on in SDK versions ICS and higher.
2215ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     *
2225ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @param event The hover event.
2235ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @return {@code true} if the event is handled
2245ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     */
225b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    public boolean dispatchHoverEvent(final MotionEvent event, final PointerTracker tracker) {
226559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        if (mView == null) {
227559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette            return false;
228559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        }
229559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette
2305ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final int x = (int) event.getX();
2315ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final int y = (int) event.getY();
2329a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final Key previousKey = mLastHoverKey;
233e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        final Key key;
234e76a9b36cabc3eb9222be245e2cf736169432cd6alanv
235e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        if (pointInView(x, y)) {
236e76a9b36cabc3eb9222be245e2cf736169432cd6alanv            key = tracker.getKeyOn(x, y);
237e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        } else {
238e76a9b36cabc3eb9222be245e2cf736169432cd6alanv            key = null;
239e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        }
2409a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mLastHoverKey = key;
2415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
2425ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        switch (event.getAction()) {
243c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_EXIT:
2446662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            // Make sure we're not getting an EXIT event because the user slid
2456662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            // off the keyboard area, then force a key press.
246e76a9b36cabc3eb9222be245e2cf736169432cd6alanv            if (key != null) {
247f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                getAccessibilityNodeProvider().simulateKeyPress(key);
2486662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            }
2496662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            //$FALL-THROUGH$
2506662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv        case MotionEvent.ACTION_HOVER_ENTER:
2519a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return onHoverKey(key, event);
252c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_MOVE:
2539a81ce92c381007affe6bb2310bf94c9856eaae1alanv            if (key != previousKey) {
2549a81ce92c381007affe6bb2310bf94c9856eaae1alanv                return onTransitionKey(key, previousKey, event);
2555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            }
256a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette            return onHoverKey(key, event);
2575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
2585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return false;
2595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
2605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
2619a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
262f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     * @return A lazily-instantiated node provider for this view proxy.
263f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     */
264f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    private AccessibilityEntityProvider getAccessibilityNodeProvider() {
265f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // Instantiate the provide only when requested. Since the system
266f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // will call this method multiple times it is a good practice to
267f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // cache the provider instance.
268f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        if (mAccessibilityNodeProvider == null) {
269f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod);
270f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        }
271f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        return mAccessibilityNodeProvider;
272f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    }
273f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
274f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    /**
275b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * Utility method to determine whether the given point, in local coordinates, is inside the
276b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * view, where the area of the view is contracted by the edge slop factor.
2776662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     *
2786662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * @param localX The local x-coordinate.
2796662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * @param localY The local y-coordinate.
2806662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     */
281b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    private boolean pointInView(final int localX, final int localY) {
2826662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv        return (localX >= mEdgeSlop) && (localY >= mEdgeSlop)
2836662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv                && (localX < (mView.getWidth() - mEdgeSlop))
2846662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv                && (localY < (mView.getHeight() - mEdgeSlop));
2856662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    }
2866662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv
2876662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    /**
288b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT on the previous key,
289b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * a HOVER_ENTER on the current key, and a HOVER_MOVE on the current key.
2909a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
2919a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param currentKey The currently hovered key.
2929a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param previousKey The previously hovered key.
2939a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param event The event that triggered the transition.
2949a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return {@code true} if the event was handled.
2959a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
296b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    private boolean onTransitionKey(final Key currentKey, final Key previousKey,
297b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa            final MotionEvent event) {
2989a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final int savedAction = event.getAction();
299c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
3009a81ce92c381007affe6bb2310bf94c9856eaae1alanv        onHoverKey(previousKey, event);
301c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        event.setAction(MotionEvent.ACTION_HOVER_ENTER);
3029a81ce92c381007affe6bb2310bf94c9856eaae1alanv        onHoverKey(currentKey, event);
303c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        event.setAction(MotionEvent.ACTION_HOVER_MOVE);
3049a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final boolean handled = onHoverKey(currentKey, event);
3059a81ce92c381007affe6bb2310bf94c9856eaae1alanv        event.setAction(savedAction);
3069a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return handled;
3079a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
3089a81ce92c381007affe6bb2310bf94c9856eaae1alanv
3099a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
310b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * Handles a hover event on a key. If {@link Key} extended View, this would be analogous to
311b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * calling View.onHoverEvent(MotionEvent).
3129a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
3139a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key The currently hovered key.
3149a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param event The hover event.
3159a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return {@code true} if the event was handled.
3169a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
317b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa    private boolean onHoverKey(final Key key, final MotionEvent event) {
3189a81ce92c381007affe6bb2310bf94c9856eaae1alanv        // Null keys can't receive events.
3199a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (key == null) {
3209a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return false;
3215ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
322f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        final AccessibilityEntityProvider provider = getAccessibilityNodeProvider();
323f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
3249a81ce92c381007affe6bb2310bf94c9856eaae1alanv        switch (event.getAction()) {
325c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_ENTER:
326f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            provider.sendAccessibilityEventForKey(
327f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
328f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            provider.performActionForKey(
329f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
3309a81ce92c381007affe6bb2310bf94c9856eaae1alanv            break;
331c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_EXIT:
332f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            provider.sendAccessibilityEventForKey(
333f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
3349a81ce92c381007affe6bb2310bf94c9856eaae1alanv            break;
3355ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
3369a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return true;
3379a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
3385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
3399a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
3408d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     * Notifies the user of changes in the keyboard shift state.
3418d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     */
3428d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    public void notifyShiftState() {
343559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        if (mView == null) {
344559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette            return;
345559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        }
346559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette
3478d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Keyboard keyboard = mView.getKeyboard();
3488d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final KeyboardId keyboardId = keyboard.mId;
3498d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final int elementId = keyboardId.mElementId;
3508d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Context context = mView.getContext();
3518d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final CharSequence text;
3528d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
3538d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        switch (elementId) {
3548d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
3558d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
3568d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            text = context.getText(R.string.spoken_description_shiftmode_locked);
3578d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
3588d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
3598d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
360e3150664ae4d8d007b8571b5bd0fd2259aac3a20Satoshi Kataoka        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
3618d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            text = context.getText(R.string.spoken_description_shiftmode_on);
3628d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
3638d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        default:
3648d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            text = context.getText(R.string.spoken_description_shiftmode_off);
3658d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        }
3665f312c9c1546da9f73d02f911d3365da4ff658fbalanv        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
3678d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    }
3688d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
3698d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    /**
3708d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     * Notifies the user of changes in the keyboard symbols state.
3718d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     */
3728d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    public void notifySymbolsState() {
373559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        if (mView == null) {
374559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette            return;
375559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        }
376559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette
3778d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Keyboard keyboard = mView.getKeyboard();
3788d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Context context = mView.getContext();
3798d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final KeyboardId keyboardId = keyboard.mId;
3808d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final int elementId = keyboardId.mElementId;
3818d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final int resId;
3828d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
3838d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        switch (elementId) {
3848d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET:
3858d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
3868d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
3878d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
3888d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
3898d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_alpha;
3908d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
3918d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_SYMBOLS:
392e3150664ae4d8d007b8571b5bd0fd2259aac3a20Satoshi Kataoka        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
3938d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_symbol;
3948d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
3958d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_PHONE:
3968d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_phone;
3978d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
3988d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
3998d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_phone_shift;
4008d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
4018d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        default:
4028d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = -1;
4038d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        }
4048d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
4058d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        if (resId < 0) {
4068d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            return;
4078d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        }
4088d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final String text = context.getString(resId);
4095f312c9c1546da9f73d02f911d3365da4ff658fbalanv        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
4108d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    }
4115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette}
412