AccessibilityUtils.java revision b0c8db018d53b103dcb4b699be27a4e1a2c2f92c
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; 245ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.util.Log; 255ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.MotionEvent; 265ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.accessibility.AccessibilityEvent; 275ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.accessibility.AccessibilityManager; 28b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport android.view.inputmethod.EditorInfo; 295ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 305ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.compat.AccessibilityManagerCompatWrapper; 31b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.compat.AudioManagerCompatWrapper; 32b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.compat.InputTypeCompatUtils; 335ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.compat.MotionEventCompatUtils; 34b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.latin.R; 355ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 365ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepublic class AccessibilityUtils { 375ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String TAG = AccessibilityUtils.class.getSimpleName(); 385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String CLASS = AccessibilityUtils.class.getClass().getName(); 395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage() 405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette .getName(); 415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 425ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final AccessibilityUtils sInstance = new AccessibilityUtils(); 435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 44b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette private Context mContext; 455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private AccessibilityManager mAccessibilityManager; 465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private AccessibilityManagerCompatWrapper mCompatManager; 47b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette private AudioManagerCompatWrapper mAudioManager; 485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 495ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /* 505ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Setting this constant to {@code false} will disable all keyboard 515ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * accessibility code, regardless of whether Accessibility is turned on in 525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the system settings. It should ONLY be used in the event of an emergency. 535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private static final boolean ENABLE_ACCESSIBILITY = true; 555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public static void init(InputMethodService inputMethod, SharedPreferences prefs) { 575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette if (!ENABLE_ACCESSIBILITY) 585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return; 595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // These only need to be initialized if the kill switch is off. 615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette sInstance.initInternal(inputMethod, prefs); 625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette KeyCodeDescriptionMapper.init(inputMethod, prefs); 635ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette AccessibleInputMethodServiceProxy.init(inputMethod, prefs); 645ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette AccessibleKeyboardViewProxy.init(inputMethod, prefs); 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 755ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette private void initInternal(Context context, SharedPreferences prefs) { 76b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette mContext = context; 775ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette mAccessibilityManager = (AccessibilityManager) context 785ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette .getSystemService(Context.ACCESSIBILITY_SERVICE); 795ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette mCompatManager = new AccessibilityManagerCompatWrapper(mAccessibilityManager); 80b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 81b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette final AudioManager audioManager = (AudioManager) context 82b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette .getSystemService(Context.AUDIO_SERVICE); 83b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette mAudioManager = new AudioManagerCompatWrapper(audioManager); 845ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 855ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 865ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 875ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Returns {@code true} if touch exploration is enabled. Currently, this 885ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * means that the kill switch is off, the device supports touch exploration, 895ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * and a spoken feedback service is turned on. 905ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 915ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @return {@code true} if touch exploration is enabled. 925ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 935ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public boolean isTouchExplorationEnabled() { 945ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return ENABLE_ACCESSIBILITY 955ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette && mAccessibilityManager.isEnabled() 96cc4b63ec4c3d3622f778c647eb584c68a3c00615Alan Viverette && mCompatManager.isTouchExplorationEnabled(); 975ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 985ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 995ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 1005ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Returns {@true} if the provided event is a touch exploration (e.g. hover) 1015ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * event. This is used to determine whether the event should be processed by 1025ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the touch exploration code within the keyboard. 1035ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 1045ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @param event The event to check. 1055ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @return {@true} is the event is a touch exploration event 1065ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 1075ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public boolean isTouchExplorationEvent(MotionEvent event) { 1085ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette final int action = event.getAction(); 1095ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return action == MotionEventCompatUtils.ACTION_HOVER_ENTER 1115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette || action == MotionEventCompatUtils.ACTION_HOVER_EXIT 1125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette || action == MotionEventCompatUtils.ACTION_HOVER_MOVE; 1135ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 1145ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette /** 116b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * @return {@code true} if the device should not speak text (eg. 117b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * non-control) characters 118b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette */ 119b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette public boolean shouldObscureInput(EditorInfo attribute) { 120b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette if (attribute == null) 121b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette return false; 122b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 123b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette // Always speak if the user is listening through headphones. 124b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) 125b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette return false; 126b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 127b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette // Don't speak if the IME is connected to a password field. 128b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette return InputTypeCompatUtils.isPasswordInputType(attribute.inputType); 129b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 130b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 131b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette /** 1325ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Sends the specified text to the {@link AccessibilityManager} to be 1335ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * spoken. 1345ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * 1355ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * @param text the text to speak 1365ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */ 1375ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette public void speak(CharSequence text) { 1385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette if (!mAccessibilityManager.isEnabled()) { 1395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette Log.e(TAG, "Attempted to speak when accessibility was disabled!"); 1405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette return; 1415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 1425ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // The following is a hack to avoid using the heavy-weight TextToSpeech 1445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // class. Instead, we're just forcing a fake AccessibilityEvent into 1455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette // the screen reader to make it speak. 1465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette final AccessibilityEvent event = AccessibilityEvent 147b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); 1485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1495ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setPackageName(PACKAGE); 1505ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setClassName(CLASS); 1515ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setEventTime(SystemClock.uptimeMillis()); 1525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.setEnabled(true); 1535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette event.getText().add(text); 1545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette 1555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette mAccessibilityManager.sendAccessibilityEvent(event); 1565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette } 157b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette 158b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette /** 159b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * Handles speaking the "connect a headset to hear passwords" notification 160b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * when connecting to a password field. 161b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * 162b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * @param attribute The input connection's editor info attribute. 163b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette * @param restarting Whether the connection is being restarted. 164b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette */ 165b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette public void onStartInputViewInternal(EditorInfo attribute, boolean restarting) { 166b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette if (shouldObscureInput(attribute)) { 167b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette final CharSequence text = mContext.getText(R.string.spoken_use_headphones); 168b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette speak(text); 169b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 170b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette } 1715ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette} 172