AccessibilityUtils.java revision 2ac5988f84b5c38d313951a3d7faddebf5f25e04
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.accessibility; 18 19import android.content.Context; 20import android.inputmethodservice.InputMethodService; 21import android.media.AudioManager; 22import android.os.SystemClock; 23import android.provider.Settings; 24import android.util.Log; 25import android.view.MotionEvent; 26import android.view.accessibility.AccessibilityEvent; 27import android.view.accessibility.AccessibilityManager; 28import android.view.inputmethod.EditorInfo; 29 30import com.android.inputmethod.compat.AccessibilityManagerCompatWrapper; 31import com.android.inputmethod.compat.AudioManagerCompatWrapper; 32import com.android.inputmethod.compat.InputTypeCompatUtils; 33import com.android.inputmethod.compat.MotionEventCompatUtils; 34import com.android.inputmethod.compat.SettingsSecureCompatUtils; 35import com.android.inputmethod.latin.R; 36 37public class AccessibilityUtils { 38 private static final String TAG = AccessibilityUtils.class.getSimpleName(); 39 private static final String CLASS = AccessibilityUtils.class.getClass().getName(); 40 private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage() 41 .getName(); 42 43 private static final AccessibilityUtils sInstance = new AccessibilityUtils(); 44 45 private Context mContext; 46 private AccessibilityManager mAccessibilityManager; 47 private AccessibilityManagerCompatWrapper mCompatManager; 48 private AudioManagerCompatWrapper mAudioManager; 49 50 /* 51 * Setting this constant to {@code false} will disable all keyboard 52 * accessibility code, regardless of whether Accessibility is turned on in 53 * the system settings. It should ONLY be used in the event of an emergency. 54 */ 55 private static final boolean ENABLE_ACCESSIBILITY = true; 56 57 public static void init(InputMethodService inputMethod) { 58 if (!ENABLE_ACCESSIBILITY) 59 return; 60 61 // These only need to be initialized if the kill switch is off. 62 sInstance.initInternal(inputMethod); 63 KeyCodeDescriptionMapper.init(); 64 AccessibleInputMethodServiceProxy.init(inputMethod); 65 AccessibleKeyboardViewProxy.init(inputMethod); 66 } 67 68 public static AccessibilityUtils getInstance() { 69 return sInstance; 70 } 71 72 private AccessibilityUtils() { 73 // This class is not publicly instantiable. 74 } 75 76 private void initInternal(Context context) { 77 mContext = context; 78 mAccessibilityManager = (AccessibilityManager) context 79 .getSystemService(Context.ACCESSIBILITY_SERVICE); 80 mCompatManager = new AccessibilityManagerCompatWrapper(mAccessibilityManager); 81 82 final AudioManager audioManager = (AudioManager) context 83 .getSystemService(Context.AUDIO_SERVICE); 84 mAudioManager = new AudioManagerCompatWrapper(audioManager); 85 } 86 87 /** 88 * Returns {@code true} if touch exploration is enabled. Currently, this 89 * means that the kill switch is off, the device supports touch exploration, 90 * and a spoken feedback service is turned on. 91 * 92 * @return {@code true} if touch exploration is enabled. 93 */ 94 public boolean isTouchExplorationEnabled() { 95 return ENABLE_ACCESSIBILITY 96 && mAccessibilityManager.isEnabled() 97 && mCompatManager.isTouchExplorationEnabled(); 98 } 99 100 /** 101 * Returns {@true} if the provided event is a touch exploration (e.g. hover) 102 * event. This is used to determine whether the event should be processed by 103 * the touch exploration code within the keyboard. 104 * 105 * @param event The event to check. 106 * @return {@true} is the event is a touch exploration event 107 */ 108 public boolean isTouchExplorationEvent(MotionEvent event) { 109 final int action = event.getAction(); 110 111 return action == MotionEventCompatUtils.ACTION_HOVER_ENTER 112 || action == MotionEventCompatUtils.ACTION_HOVER_EXIT 113 || action == MotionEventCompatUtils.ACTION_HOVER_MOVE; 114 } 115 116 /** 117 * Returns whether the device should obscure typed password characters. 118 * Typically this means speaking "dot" in place of non-control characters. 119 * 120 * @return {@code true} if the device should obscure password characters. 121 */ 122 public boolean shouldObscureInput(EditorInfo editorInfo) { 123 if (editorInfo == null) 124 return false; 125 126 // The user can optionally force speaking passwords. 127 if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) { 128 final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(), 129 SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; 130 if (speakPassword) 131 return false; 132 } 133 134 // Always speak if the user is listening through headphones. 135 if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) 136 return false; 137 138 // Don't speak if the IME is connected to a password field. 139 return InputTypeCompatUtils.isPasswordInputType(editorInfo.inputType); 140 } 141 142 /** 143 * Sends the specified text to the {@link AccessibilityManager} to be 144 * spoken. 145 * 146 * @param text the text to speak 147 */ 148 public void speak(CharSequence text) { 149 if (!mAccessibilityManager.isEnabled()) { 150 Log.e(TAG, "Attempted to speak when accessibility was disabled!"); 151 return; 152 } 153 154 // The following is a hack to avoid using the heavy-weight TextToSpeech 155 // class. Instead, we're just forcing a fake AccessibilityEvent into 156 // the screen reader to make it speak. 157 final AccessibilityEvent event = AccessibilityEvent 158 .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); 159 160 event.setPackageName(PACKAGE); 161 event.setClassName(CLASS); 162 event.setEventTime(SystemClock.uptimeMillis()); 163 event.setEnabled(true); 164 event.getText().add(text); 165 166 mAccessibilityManager.sendAccessibilityEvent(event); 167 } 168 169 /** 170 * Handles speaking the "connect a headset to hear passwords" notification 171 * when connecting to a password field. 172 * 173 * @param editorInfo The input connection's editor info attribute. 174 * @param restarting Whether the connection is being restarted. 175 */ 176 public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) { 177 if (shouldObscureInput(editorInfo)) { 178 final CharSequence text = mContext.getText(R.string.spoken_use_headphones); 179 speak(text); 180 } 181 } 182} 183