15ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette/* 25ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Copyright (C) 2011 The Android Open Source Project 35ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 45ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License"); 55ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * you may not use this file except in compliance with the License. 65ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * You may obtain a copy of the License at 75ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 85ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * http://www.apache.org/licenses/LICENSE-2.0 95ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Unless required by applicable law or agreed to in writing, software 115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS, 125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * See the License for the specific language governing permissions and 145ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * limitations under the License. 155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 175ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepackage com.android.inputmethod.accessibility; 185ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 195ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.content.Context; 205ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.inputmethodservice.InputMethodService; 21b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport android.media.AudioManager; 225f312c9c1546da9f73d02f911d3365da4ff658fbalanvimport android.os.Build; 235ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.os.SystemClock; 24c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viveretteimport android.provider.Settings; 255f312c9c1546da9f73d02f911d3365da4ff658fbalanvimport android.support.v4.view.accessibility.AccessibilityEventCompat; 265ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.util.Log; 275ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.MotionEvent; 285f312c9c1546da9f73d02f911d3365da4ff658fbalanvimport android.view.View; 295f312c9c1546da9f73d02f911d3365da4ff658fbalanvimport android.view.ViewGroup; 305f312c9c1546da9f73d02f911d3365da4ff658fbalanvimport android.view.ViewParent; 315ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.accessibility.AccessibilityEvent; 325ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.accessibility.AccessibilityManager; 33b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport android.view.inputmethod.EditorInfo; 345ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 35c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viveretteimport com.android.inputmethod.compat.SettingsSecureCompatUtils; 36be55086fd9218bc03ee0ccac1052d96b40d8a979Tadashi G. Takaokaimport com.android.inputmethod.latin.InputTypeUtils; 37b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.latin.R; 385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 391e11c44d1b5f9ddf593c5407cb14c458be0056f2Tadashi G. Takaokapublic final class AccessibilityUtils { 405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String TAG = AccessibilityUtils.class.getSimpleName(); 415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String CLASS = AccessibilityUtils.class.getClass().getName(); 421e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa private static final String PACKAGE = 431e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa AccessibilityUtils.class.getClass().getPackage().getName(); 445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final AccessibilityUtils sInstance = new AccessibilityUtils(); 465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 47b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette private Context mContext; 485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private AccessibilityManager mAccessibilityManager; 491e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa private AudioManager mAudioManager; 505ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 515ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /* 525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Setting this constant to {@code false} will disable all keyboard 535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * accessibility code, regardless of whether Accessibility is turned on in 545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the system settings. It should ONLY be used in the event of an emergency. 555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final boolean ENABLE_ACCESSIBILITY = true; 575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 58b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa public static void init(final InputMethodService inputMethod) { 591e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa if (!ENABLE_ACCESSIBILITY) return; 605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // These only need to be initialized if the kill switch is off. 622ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka sInstance.initInternal(inputMethod); 632ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka KeyCodeDescriptionMapper.init(); 642ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka AccessibleKeyboardViewProxy.init(inputMethod); 655ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 665ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 675ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public static AccessibilityUtils getInstance() { 685ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return sInstance; 695ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 705ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 715ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private AccessibilityUtils() { 725ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // This class is not publicly instantiable. 735ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 745ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 75b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa private void initInternal(final Context context) { 76b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette mContext = context; 771e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa mAccessibilityManager = 781e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); 791e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 805ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 815ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 825ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 83c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette * Returns {@code true} if accessibility is enabled. Currently, this means 84c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette * that the kill switch is off and system accessibility is turned on. 85c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette * 86c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette * @return {@code true} if accessibility is enabled. 87c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette */ 88c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette public boolean isAccessibilityEnabled() { 89c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette return ENABLE_ACCESSIBILITY && mAccessibilityManager.isEnabled(); 90c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette } 91c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette 92c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette /** 935ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Returns {@code true} if touch exploration is enabled. Currently, this 945ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * means that the kill switch is off, the device supports touch exploration, 95c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette * and system accessibility is turned on. 965ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 975ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @return {@code true} if touch exploration is enabled. 985ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 995ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public boolean isTouchExplorationEnabled() { 100c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette return isAccessibilityEnabled() && mAccessibilityManager.isTouchExplorationEnabled(); 1015ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 1025ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1035ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 1045ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Returns {@true} if the provided event is a touch exploration (e.g. hover) 1055ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * event. This is used to determine whether the event should be processed by 1065ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the touch exploration code within the keyboard. 1075ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 1085ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @param event The event to check. 1095ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @return {@true} is the event is a touch exploration event 1105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 111b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa public boolean isTouchExplorationEvent(final MotionEvent event) { 1125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette final int action = event.getAction(); 113c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka return action == MotionEvent.ACTION_HOVER_ENTER 114c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka || action == MotionEvent.ACTION_HOVER_EXIT 115c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka || action == MotionEvent.ACTION_HOVER_MOVE; 1165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 1175ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1185ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 119c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * Returns whether the device should obscure typed password characters. 120c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * Typically this means speaking "dot" in place of non-control characters. 1219a81ce92c381007affe6bb2310bf94c9856eaae1alanv * 122c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * @return {@code true} if the device should obscure password characters. 123b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette */ 124c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette @SuppressWarnings("deprecation") 125b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa public boolean shouldObscureInput(final EditorInfo editorInfo) { 1261e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa if (editorInfo == null) return false; 127b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 128c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette // The user can optionally force speaking passwords. 129c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) { 130c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(), 131c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; 1321e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa if (speakPassword) return false; 133c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette } 134c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette 135b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette // Always speak if the user is listening through headphones. 1361e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) { 137b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette return false; 1381e6edb3e5728f82d45bc2677fd72aa654b37ee73Ken Wakasa } 139b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 140b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette // Don't speak if the IME is connected to a password field. 141be55086fd9218bc03ee0ccac1052d96b40d8a979Tadashi G. Takaoka return InputTypeUtils.isPasswordInputType(editorInfo.inputType); 142b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 143b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 144b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette /** 1455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Sends the specified text to the {@link AccessibilityManager} to be 1465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * spoken. 1475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 1485f312c9c1546da9f73d02f911d3365da4ff658fbalanv * @param view The source view. 1495f312c9c1546da9f73d02f911d3365da4ff658fbalanv * @param text The text to speak. 1505ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 151b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa public void announceForAccessibility(final View view, final CharSequence text) { 1525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette if (!mAccessibilityManager.isEnabled()) { 1535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette Log.e(TAG, "Attempted to speak when accessibility was disabled!"); 1545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return; 1555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 1565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // The following is a hack to avoid using the heavy-weight TextToSpeech 1585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // class. Instead, we're just forcing a fake AccessibilityEvent into 1595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // the screen reader to make it speak. 1605f312c9c1546da9f73d02f911d3365da4ff658fbalanv final AccessibilityEvent event = AccessibilityEvent.obtain(); 1615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setPackageName(PACKAGE); 1635ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setClassName(CLASS); 1645ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setEventTime(SystemClock.uptimeMillis()); 1655ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setEnabled(true); 1665ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.getText().add(text); 1675ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 168b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa // Platforms starting at SDK version 16 (Build.VERSION_CODES.JELLY_BEAN) should use 169b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa // announce events. 170b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 1715f312c9c1546da9f73d02f911d3365da4ff658fbalanv event.setEventType(AccessibilityEventCompat.TYPE_ANNOUNCEMENT); 1725f312c9c1546da9f73d02f911d3365da4ff658fbalanv } else { 1735f312c9c1546da9f73d02f911d3365da4ff658fbalanv event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED); 1745f312c9c1546da9f73d02f911d3365da4ff658fbalanv } 1755f312c9c1546da9f73d02f911d3365da4ff658fbalanv 1765f312c9c1546da9f73d02f911d3365da4ff658fbalanv final ViewParent viewParent = view.getParent(); 1775f312c9c1546da9f73d02f911d3365da4ff658fbalanv if ((viewParent == null) || !(viewParent instanceof ViewGroup)) { 1785f312c9c1546da9f73d02f911d3365da4ff658fbalanv Log.e(TAG, "Failed to obtain ViewParent in announceForAccessibility"); 1795f312c9c1546da9f73d02f911d3365da4ff658fbalanv return; 1805f312c9c1546da9f73d02f911d3365da4ff658fbalanv } 1815f312c9c1546da9f73d02f911d3365da4ff658fbalanv 1825f312c9c1546da9f73d02f911d3365da4ff658fbalanv viewParent.requestSendAccessibilityEvent(view, event); 1835ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 184b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 185b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette /** 186b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * Handles speaking the "connect a headset to hear passwords" notification 187b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * when connecting to a password field. 188b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * 1895f312c9c1546da9f73d02f911d3365da4ff658fbalanv * @param view The source view. 190e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka * @param editorInfo The input connection's editor info attribute. 191b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * @param restarting Whether the connection is being restarted. 192b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette */ 193b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa public void onStartInputViewInternal(final View view, final EditorInfo editorInfo, 194b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa final boolean restarting) { 195e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka if (shouldObscureInput(editorInfo)) { 196b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette final CharSequence text = mContext.getText(R.string.spoken_use_headphones); 1975f312c9c1546da9f73d02f911d3365da4ff658fbalanv announceForAccessibility(view, text); 198b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 199b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 200282adf733093b41a31514746825ea05fc90fb3eealanv 201282adf733093b41a31514746825ea05fc90fb3eealanv /** 202282adf733093b41a31514746825ea05fc90fb3eealanv * Sends the specified {@link AccessibilityEvent} if accessibility is 203282adf733093b41a31514746825ea05fc90fb3eealanv * enabled. No operation if accessibility is disabled. 204282adf733093b41a31514746825ea05fc90fb3eealanv * 205282adf733093b41a31514746825ea05fc90fb3eealanv * @param event The event to send. 206282adf733093b41a31514746825ea05fc90fb3eealanv */ 207b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa public void requestSendAccessibilityEvent(final AccessibilityEvent event) { 208282adf733093b41a31514746825ea05fc90fb3eealanv if (mAccessibilityManager.isEnabled()) { 209282adf733093b41a31514746825ea05fc90fb3eealanv mAccessibilityManager.sendAccessibilityEvent(event); 210282adf733093b41a31514746825ea05fc90fb3eealanv } 211282adf733093b41a31514746825ea05fc90fb3eealanv } 2125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette} 213