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