AccessibilityUtils.java revision c960695f38ae0564dff3a6897fd1843c8e74c604
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.content.SharedPreferences; 215ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.inputmethodservice.InputMethodService; 22b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport android.media.AudioManager; 235ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.os.SystemClock; 24c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viveretteimport android.provider.Settings; 255ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.util.Log; 265ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.MotionEvent; 275ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.accessibility.AccessibilityEvent; 285ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.accessibility.AccessibilityManager; 29b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport android.view.inputmethod.EditorInfo; 305ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 315ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.compat.AccessibilityManagerCompatWrapper; 32b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.compat.AudioManagerCompatWrapper; 33b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.compat.InputTypeCompatUtils; 345ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.compat.MotionEventCompatUtils; 35c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viveretteimport com.android.inputmethod.compat.SettingsSecureCompatUtils; 36b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.latin.R; 375ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepublic class AccessibilityUtils { 395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String TAG = AccessibilityUtils.class.getSimpleName(); 405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String CLASS = AccessibilityUtils.class.getClass().getName(); 415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage() 425ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette .getName(); 435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final AccessibilityUtils sInstance = new AccessibilityUtils(); 455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 46b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette private Context mContext; 475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private AccessibilityManager mAccessibilityManager; 485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private AccessibilityManagerCompatWrapper mCompatManager; 49b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette private AudioManagerCompatWrapper 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 585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public static void init(InputMethodService inputMethod, SharedPreferences prefs) { 595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette if (!ENABLE_ACCESSIBILITY) 605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return; 615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // These only need to be initialized if the kill switch is off. 635ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette sInstance.initInternal(inputMethod, prefs); 645ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette KeyCodeDescriptionMapper.init(inputMethod, prefs); 655ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette AccessibleInputMethodServiceProxy.init(inputMethod, prefs); 665ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette AccessibleKeyboardViewProxy.init(inputMethod, prefs); 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 775ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private void initInternal(Context context, SharedPreferences prefs) { 78b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette mContext = context; 795ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette mAccessibilityManager = (AccessibilityManager) context 805ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette .getSystemService(Context.ACCESSIBILITY_SERVICE); 815ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette mCompatManager = new AccessibilityManagerCompatWrapper(mAccessibilityManager); 82b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 83b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette final AudioManager audioManager = (AudioManager) context 84b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette .getSystemService(Context.AUDIO_SERVICE); 85b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette mAudioManager = new AudioManagerCompatWrapper(audioManager); 865ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 875ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 885ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 895ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Returns {@code true} if touch exploration is enabled. Currently, this 905ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * means that the kill switch is off, the device supports touch exploration, 915ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * and a spoken feedback service is turned on. 925ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 935ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @return {@code true} if touch exploration is enabled. 945ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 955ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public boolean isTouchExplorationEnabled() { 965ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return ENABLE_ACCESSIBILITY 975ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette && mAccessibilityManager.isEnabled() 98cc4b63ec4c3d3622f778c647eb584c68a3c00615Alan Viverette && mCompatManager.isTouchExplorationEnabled(); 995ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 1005ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1015ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 1025ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Returns {@true} if the provided event is a touch exploration (e.g. hover) 1035ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * event. This is used to determine whether the event should be processed by 1045ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the touch exploration code within the keyboard. 1055ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 1065ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @param event The event to check. 1075ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @return {@true} is the event is a touch exploration event 1085ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 1095ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public boolean isTouchExplorationEvent(MotionEvent event) { 1105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette final int action = event.getAction(); 1115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return action == MotionEventCompatUtils.ACTION_HOVER_ENTER 1135ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette || action == MotionEventCompatUtils.ACTION_HOVER_EXIT 1145ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette || action == MotionEventCompatUtils.ACTION_HOVER_MOVE; 1155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 1165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1175ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 118c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * Returns whether the device should obscure typed password characters. 119c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * Typically this means speaking "dot" in place of non-control characters. 120c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * 121c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette * @return {@code true} if the device should obscure password characters. 122b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette */ 123b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette public boolean shouldObscureInput(EditorInfo attribute) { 124b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette if (attribute == null) 125b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette return false; 126b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 127c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette // The user can optionally force speaking passwords. 128c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) { 129c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(), 130c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; 131c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette if (speakPassword) 132c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette return false; 133c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette } 134c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette 135b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette // Always speak if the user is listening through headphones. 136b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) 137b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette return false; 138b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 139b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette // Don't speak if the IME is connected to a password field. 140b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette return InputTypeCompatUtils.isPasswordInputType(attribute.inputType); 141b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 142b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 143b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette /** 1445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Sends the specified text to the {@link AccessibilityManager} to be 1455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * spoken. 1465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 1475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @param text the text to speak 1485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 1495ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public void speak(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. 1585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette final AccessibilityEvent event = AccessibilityEvent 159b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); 1605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setPackageName(PACKAGE); 1625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setClassName(CLASS); 1635ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setEventTime(SystemClock.uptimeMillis()); 1645ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setEnabled(true); 1655ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.getText().add(text); 1665ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1675ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette mAccessibilityManager.sendAccessibilityEvent(event); 1685ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 169b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 170b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette /** 171b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * Handles speaking the "connect a headset to hear passwords" notification 172b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * when connecting to a password field. 173b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * 174b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * @param attribute The input connection's editor info attribute. 175b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * @param restarting Whether the connection is being restarted. 176b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette */ 177b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette public void onStartInputViewInternal(EditorInfo attribute, boolean restarting) { 178b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette if (shouldObscureInput(attribute)) { 179b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette final CharSequence text = mContext.getText(R.string.spoken_use_headphones); 180b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette speak(text); 181b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 182b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 1835ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette} 184