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 35b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.compat.AudioManagerCompatWrapper; 36c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viveretteimport com.android.inputmethod.compat.SettingsSecureCompatUtils; 37be55086fd9218bc03ee0ccac1052d96b40d8a979Tadashi G. Takaokaimport com.android.inputmethod.latin.InputTypeUtils; 38b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.latin.R; 395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 401e11c44d1b5f9ddf593c5407cb14c458be0056f2Tadashi G. Takaokapublic final class AccessibilityUtils { 415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String TAG = AccessibilityUtils.class.getSimpleName(); 425ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String CLASS = AccessibilityUtils.class.getClass().getName(); 435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage() 445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette .getName(); 455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final AccessibilityUtils sInstance = new AccessibilityUtils(); 475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 48b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette private Context mContext; 495ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private AccessibilityManager mAccessibilityManager; 50b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette private AudioManagerCompatWrapper mAudioManager; 515ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /* 535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Setting this constant to {@code false} will disable all keyboard 545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * accessibility code, regardless of whether Accessibility is turned on in 555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the system settings. It should ONLY be used in the event of an emergency. 565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final boolean ENABLE_ACCESSIBILITY = true; 585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 592ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka public static void init(InputMethodService inputMethod) { 605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette if (!ENABLE_ACCESSIBILITY) 615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return; 625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 635ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // These only need to be initialized if the kill switch is off. 642ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka sInstance.initInternal(inputMethod); 652ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka KeyCodeDescriptionMapper.init(); 662ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka AccessibleKeyboardViewProxy.init(inputMethod); 675ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 685ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 695ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public static AccessibilityUtils getInstance() { 705ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return sInstance; 715ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 725ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 735ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private AccessibilityUtils() { 745ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // This class is not publicly instantiable. 755ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 765ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 772ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka private void initInternal(Context context) { 78b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette mContext = context; 795ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette mAccessibilityManager = (AccessibilityManager) context 805ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette .getSystemService(Context.ACCESSIBILITY_SERVICE); 81b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 82b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette final AudioManager audioManager = (AudioManager) context 83b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette .getSystemService(Context.AUDIO_SERVICE); 84b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette mAudioManager = new AudioManagerCompatWrapper(audioManager); 855ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 865ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 875ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 885ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Returns {@code true} if touch exploration is enabled. Currently, this 895ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * means that the kill switch is off, the device supports touch exploration, 905ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * and a spoken feedback service is turned on. 915ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 925ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @return {@code true} if touch exploration is enabled. 935ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 945ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public boolean isTouchExplorationEnabled() { 955ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return ENABLE_ACCESSIBILITY 965ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette && mAccessibilityManager.isEnabled() 97c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka && mAccessibilityManager.isTouchExplorationEnabled(); 985ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 995ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1005ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 1015ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Returns {@true} if the provided event is a touch exploration (e.g. hover) 1025ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * event. This is used to determine whether the event should be processed by 1035ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the touch exploration code within the keyboard. 1045ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 1055ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @param event The event to check. 1065ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @return {@true} is the event is a touch exploration event 1075ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 1085ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public boolean isTouchExplorationEvent(MotionEvent event) { 1095ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette final int action = event.getAction(); 1105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 111c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka return action == MotionEvent.ACTION_HOVER_ENTER 112c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka || action == MotionEvent.ACTION_HOVER_EXIT 113c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka || action == MotionEvent.ACTION_HOVER_MOVE; 1145ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 1155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 117c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * Returns whether the device should obscure typed password characters. 118c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * Typically this means speaking "dot" in place of non-control characters. 1199a81ce92c381007affe6bb2310bf94c9856eaae1alanv * 120c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * @return {@code true} if the device should obscure password characters. 121b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette */ 122e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka public boolean shouldObscureInput(EditorInfo editorInfo) { 123e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka if (editorInfo == null) 124b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette return false; 125b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 126c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette // The user can optionally force speaking passwords. 127c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) { 128c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(), 129c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; 130c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette if (speakPassword) 131c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette return false; 132c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette } 133c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette 134b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette // Always speak if the user is listening through headphones. 135b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) 136b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette return false; 137b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 138b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette // Don't speak if the IME is connected to a password field. 139be55086fd9218bc03ee0ccac1052d96b40d8a979Tadashi G. Takaoka return InputTypeUtils.isPasswordInputType(editorInfo.inputType); 140b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 141b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 142b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette /** 1435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Sends the specified text to the {@link AccessibilityManager} to be 1445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * spoken. 1455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 1465f312c9c1546da9f73d02f911d3365da4ff658fbalanv * @param view The source view. 1475f312c9c1546da9f73d02f911d3365da4ff658fbalanv * @param text The text to speak. 1485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 1495f312c9c1546da9f73d02f911d3365da4ff658fbalanv public void announceForAccessibility(View view, CharSequence text) { 1505ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette if (!mAccessibilityManager.isEnabled()) { 1515ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette Log.e(TAG, "Attempted to speak when accessibility was disabled!"); 1525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return; 1535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 1545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // The following is a hack to avoid using the heavy-weight TextToSpeech 1565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // class. Instead, we're just forcing a fake AccessibilityEvent into 1575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // the screen reader to make it speak. 1585f312c9c1546da9f73d02f911d3365da4ff658fbalanv final AccessibilityEvent event = AccessibilityEvent.obtain(); 1595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setPackageName(PACKAGE); 1615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setClassName(CLASS); 1625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setEventTime(SystemClock.uptimeMillis()); 1635ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setEnabled(true); 1645ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.getText().add(text); 1655ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1665f312c9c1546da9f73d02f911d3365da4ff658fbalanv // Platforms starting at SDK 16 should use announce events. 1675f312c9c1546da9f73d02f911d3365da4ff658fbalanv if (Build.VERSION.SDK_INT >= 16) { 1685f312c9c1546da9f73d02f911d3365da4ff658fbalanv event.setEventType(AccessibilityEventCompat.TYPE_ANNOUNCEMENT); 1695f312c9c1546da9f73d02f911d3365da4ff658fbalanv } else { 1705f312c9c1546da9f73d02f911d3365da4ff658fbalanv event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED); 1715f312c9c1546da9f73d02f911d3365da4ff658fbalanv } 1725f312c9c1546da9f73d02f911d3365da4ff658fbalanv 1735f312c9c1546da9f73d02f911d3365da4ff658fbalanv final ViewParent viewParent = view.getParent(); 1745f312c9c1546da9f73d02f911d3365da4ff658fbalanv if ((viewParent == null) || !(viewParent instanceof ViewGroup)) { 1755f312c9c1546da9f73d02f911d3365da4ff658fbalanv Log.e(TAG, "Failed to obtain ViewParent in announceForAccessibility"); 1765f312c9c1546da9f73d02f911d3365da4ff658fbalanv return; 1775f312c9c1546da9f73d02f911d3365da4ff658fbalanv } 1785f312c9c1546da9f73d02f911d3365da4ff658fbalanv 1795f312c9c1546da9f73d02f911d3365da4ff658fbalanv viewParent.requestSendAccessibilityEvent(view, event); 1805ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 181b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 182b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette /** 183b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * Handles speaking the "connect a headset to hear passwords" notification 184b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * when connecting to a password field. 185b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * 1865f312c9c1546da9f73d02f911d3365da4ff658fbalanv * @param view The source view. 187e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka * @param editorInfo The input connection's editor info attribute. 188b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * @param restarting Whether the connection is being restarted. 189b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette */ 1905f312c9c1546da9f73d02f911d3365da4ff658fbalanv public void onStartInputViewInternal(View view, EditorInfo editorInfo, boolean restarting) { 191e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka if (shouldObscureInput(editorInfo)) { 192b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette final CharSequence text = mContext.getText(R.string.spoken_use_headphones); 1935f312c9c1546da9f73d02f911d3365da4ff658fbalanv announceForAccessibility(view, text); 194b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 195b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 196282adf733093b41a31514746825ea05fc90fb3eealanv 197282adf733093b41a31514746825ea05fc90fb3eealanv /** 198282adf733093b41a31514746825ea05fc90fb3eealanv * Sends the specified {@link AccessibilityEvent} if accessibility is 199282adf733093b41a31514746825ea05fc90fb3eealanv * enabled. No operation if accessibility is disabled. 200282adf733093b41a31514746825ea05fc90fb3eealanv * 201282adf733093b41a31514746825ea05fc90fb3eealanv * @param event The event to send. 202282adf733093b41a31514746825ea05fc90fb3eealanv */ 203282adf733093b41a31514746825ea05fc90fb3eealanv public void requestSendAccessibilityEvent(AccessibilityEvent event) { 204282adf733093b41a31514746825ea05fc90fb3eealanv if (mAccessibilityManager.isEnabled()) { 205282adf733093b41a31514746825ea05fc90fb3eealanv mAccessibilityManager.sendAccessibilityEvent(event); 206282adf733093b41a31514746825ea05fc90fb3eealanv } 207282adf733093b41a31514746825ea05fc90fb3eealanv } 2085ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette} 209