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