AccessibilityUtils.java revision c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3
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.AudioManagerCompatWrapper; 31import com.android.inputmethod.compat.InputTypeCompatUtils; 32import com.android.inputmethod.compat.SettingsSecureCompatUtils; 33import com.android.inputmethod.latin.R; 34 35public class AccessibilityUtils { 36 private static final String TAG = AccessibilityUtils.class.getSimpleName(); 37 private static final String CLASS = AccessibilityUtils.class.getClass().getName(); 38 private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage() 39 .getName(); 40 41 private static final AccessibilityUtils sInstance = new AccessibilityUtils(); 42 43 private Context mContext; 44 private AccessibilityManager mAccessibilityManager; 45 private AudioManagerCompatWrapper mAudioManager; 46 47 /* 48 * Setting this constant to {@code false} will disable all keyboard 49 * accessibility code, regardless of whether Accessibility is turned on in 50 * the system settings. It should ONLY be used in the event of an emergency. 51 */ 52 private static final boolean ENABLE_ACCESSIBILITY = true; 53 54 public static void init(InputMethodService inputMethod) { 55 if (!ENABLE_ACCESSIBILITY) 56 return; 57 58 // These only need to be initialized if the kill switch is off. 59 sInstance.initInternal(inputMethod); 60 KeyCodeDescriptionMapper.init(); 61 AccessibleInputMethodServiceProxy.init(inputMethod); 62 AccessibleKeyboardViewProxy.init(inputMethod); 63 } 64 65 public static AccessibilityUtils getInstance() { 66 return sInstance; 67 } 68 69 private AccessibilityUtils() { 70 // This class is not publicly instantiable. 71 } 72 73 private void initInternal(Context context) { 74 mContext = context; 75 mAccessibilityManager = (AccessibilityManager) context 76 .getSystemService(Context.ACCESSIBILITY_SERVICE); 77 78 final AudioManager audioManager = (AudioManager) context 79 .getSystemService(Context.AUDIO_SERVICE); 80 mAudioManager = new AudioManagerCompatWrapper(audioManager); 81 } 82 83 /** 84 * Returns {@code true} if touch exploration is enabled. Currently, this 85 * means that the kill switch is off, the device supports touch exploration, 86 * and a spoken feedback service is turned on. 87 * 88 * @return {@code true} if touch exploration is enabled. 89 */ 90 public boolean isTouchExplorationEnabled() { 91 return ENABLE_ACCESSIBILITY 92 && mAccessibilityManager.isEnabled() 93 && mAccessibilityManager.isTouchExplorationEnabled(); 94 } 95 96 /** 97 * Returns {@true} if the provided event is a touch exploration (e.g. hover) 98 * event. This is used to determine whether the event should be processed by 99 * the touch exploration code within the keyboard. 100 * 101 * @param event The event to check. 102 * @return {@true} is the event is a touch exploration event 103 */ 104 public boolean isTouchExplorationEvent(MotionEvent event) { 105 final int action = event.getAction(); 106 107 return action == MotionEvent.ACTION_HOVER_ENTER 108 || action == MotionEvent.ACTION_HOVER_EXIT 109 || action == MotionEvent.ACTION_HOVER_MOVE; 110 } 111 112 /** 113 * Returns whether the device should obscure typed password characters. 114 * Typically this means speaking "dot" in place of non-control characters. 115 * 116 * @return {@code true} if the device should obscure password characters. 117 */ 118 public boolean shouldObscureInput(EditorInfo editorInfo) { 119 if (editorInfo == null) 120 return false; 121 122 // The user can optionally force speaking passwords. 123 if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) { 124 final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(), 125 SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; 126 if (speakPassword) 127 return false; 128 } 129 130 // Always speak if the user is listening through headphones. 131 if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) 132 return false; 133 134 // Don't speak if the IME is connected to a password field. 135 return InputTypeCompatUtils.isPasswordInputType(editorInfo.inputType); 136 } 137 138 /** 139 * Sends the specified text to the {@link AccessibilityManager} to be 140 * spoken. 141 * 142 * @param text the text to speak 143 */ 144 public void speak(CharSequence text) { 145 if (!mAccessibilityManager.isEnabled()) { 146 Log.e(TAG, "Attempted to speak when accessibility was disabled!"); 147 return; 148 } 149 150 // The following is a hack to avoid using the heavy-weight TextToSpeech 151 // class. Instead, we're just forcing a fake AccessibilityEvent into 152 // the screen reader to make it speak. 153 final AccessibilityEvent event = AccessibilityEvent 154 .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); 155 156 event.setPackageName(PACKAGE); 157 event.setClassName(CLASS); 158 event.setEventTime(SystemClock.uptimeMillis()); 159 event.setEnabled(true); 160 event.getText().add(text); 161 162 mAccessibilityManager.sendAccessibilityEvent(event); 163 } 164 165 /** 166 * Handles speaking the "connect a headset to hear passwords" notification 167 * when connecting to a password field. 168 * 169 * @param editorInfo The input connection's editor info attribute. 170 * @param restarting Whether the connection is being restarted. 171 */ 172 public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) { 173 if (shouldObscureInput(editorInfo)) { 174 final CharSequence text = mContext.getText(R.string.spoken_use_headphones); 175 speak(text); 176 } 177 } 178} 179