MainKeyboardAccessibilityDelegate.java revision 176f803176de964cbb3715cfe033797de62aa1fe
1/* 2 * Copyright (C) 2014 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.graphics.Rect; 21import android.os.SystemClock; 22import android.util.Log; 23import android.util.SparseIntArray; 24import android.view.MotionEvent; 25 26import com.android.inputmethod.keyboard.Key; 27import com.android.inputmethod.keyboard.KeyDetector; 28import com.android.inputmethod.keyboard.Keyboard; 29import com.android.inputmethod.keyboard.KeyboardId; 30import com.android.inputmethod.keyboard.MainKeyboardView; 31import com.android.inputmethod.keyboard.PointerTracker; 32import com.android.inputmethod.latin.R; 33import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 34 35/** 36 * This class represents a delegate that can be registered in {@link MainKeyboardView} to enhance 37 * accessibility support via composition rather via inheritance. 38 */ 39public final class MainKeyboardAccessibilityDelegate 40 extends KeyboardAccessibilityDelegate<MainKeyboardView> 41 implements AccessibilityLongPressTimer.LongPressTimerCallback { 42 private static final String TAG = MainKeyboardAccessibilityDelegate.class.getSimpleName(); 43 44 /** Map of keyboard modes to resource IDs. */ 45 private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray(); 46 47 static { 48 KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATE, R.string.keyboard_mode_date); 49 KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATETIME, R.string.keyboard_mode_date_time); 50 KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_EMAIL, R.string.keyboard_mode_email); 51 KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_IM, R.string.keyboard_mode_im); 52 KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_NUMBER, R.string.keyboard_mode_number); 53 KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_PHONE, R.string.keyboard_mode_phone); 54 KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TEXT, R.string.keyboard_mode_text); 55 KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TIME, R.string.keyboard_mode_time); 56 KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url); 57 } 58 59 /** The most recently set keyboard mode. */ 60 private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN; 61 private static final int KEYBOARD_IS_HIDDEN = -1; 62 // The rectangle region to ignore hover events. 63 private final Rect mBoundsToIgnoreHoverEvent = new Rect(); 64 65 private final AccessibilityLongPressTimer mAccessibilityLongPressTimer; 66 67 public MainKeyboardAccessibilityDelegate(final MainKeyboardView mainKeyboardView, 68 final KeyDetector keyDetector) { 69 super(mainKeyboardView, keyDetector); 70 mAccessibilityLongPressTimer = new AccessibilityLongPressTimer( 71 this /* callback */, mainKeyboardView.getContext()); 72 } 73 74 /** 75 * {@inheritDoc} 76 */ 77 @Override 78 public void setKeyboard(final Keyboard keyboard) { 79 if (keyboard == null) { 80 return; 81 } 82 final Keyboard lastKeyboard = getKeyboard(); 83 super.setKeyboard(keyboard); 84 final int lastKeyboardMode = mLastKeyboardMode; 85 mLastKeyboardMode = keyboard.mId.mMode; 86 87 // Since this method is called even when accessibility is off, make sure 88 // to check the state before announcing anything. 89 if (!AccessibilityUtils.getInstance().isAccessibilityEnabled()) { 90 return; 91 } 92 // Announce the language name only when the language is changed. 93 if (lastKeyboard == null || !keyboard.mId.mSubtype.equals(lastKeyboard.mId.mSubtype)) { 94 announceKeyboardLanguage(keyboard); 95 return; 96 } 97 // Announce the mode only when the mode is changed. 98 if (keyboard.mId.mMode != lastKeyboardMode) { 99 announceKeyboardMode(keyboard); 100 return; 101 } 102 // Announce the keyboard type only when the type is changed. 103 if (keyboard.mId.mElementId != lastKeyboard.mId.mElementId) { 104 announceKeyboardType(keyboard, lastKeyboard); 105 return; 106 } 107 } 108 109 /** 110 * Called when the keyboard is hidden and accessibility is enabled. 111 */ 112 public void onHideWindow() { 113 announceKeyboardHidden(); 114 mLastKeyboardMode = KEYBOARD_IS_HIDDEN; 115 } 116 117 /** 118 * Announces which language of keyboard is being displayed. 119 * 120 * @param keyboard The new keyboard. 121 */ 122 private void announceKeyboardLanguage(final Keyboard keyboard) { 123 final String languageText = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale( 124 keyboard.mId.mSubtype); 125 sendWindowStateChanged(languageText); 126 } 127 128 /** 129 * Announces which type of keyboard is being displayed. 130 * If the keyboard type is unknown, no announcement is made. 131 * 132 * @param keyboard The new keyboard. 133 */ 134 private void announceKeyboardMode(final Keyboard keyboard) { 135 final Context context = mKeyboardView.getContext(); 136 final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(keyboard.mId.mMode); 137 if (modeTextResId == 0) { 138 return; 139 } 140 final String modeText = context.getString(modeTextResId); 141 final String text = context.getString(R.string.announce_keyboard_mode, modeText); 142 sendWindowStateChanged(text); 143 } 144 145 /** 146 * Announces which type of keyboard is being displayed. 147 * 148 * @param keyboard The new keyboard. 149 * @param lastKeyboard The last keyboard. 150 */ 151 private void announceKeyboardType(final Keyboard keyboard, final Keyboard lastKeyboard) { 152 final int lastElementId = lastKeyboard.mId.mElementId; 153 final int resId; 154 switch (keyboard.mId.mElementId) { 155 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 156 case KeyboardId.ELEMENT_ALPHABET: 157 if (lastElementId == KeyboardId.ELEMENT_ALPHABET 158 || lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { 159 return; 160 } 161 resId = R.string.spoken_description_mode_alpha; 162 break; 163 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 164 resId = R.string.spoken_description_shiftmode_on; 165 break; 166 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 167 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 168 resId = R.string.spoken_description_shiftmode_locked; 169 break; 170 case KeyboardId.ELEMENT_SYMBOLS: 171 resId = R.string.spoken_description_mode_symbol; 172 break; 173 case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: 174 resId = R.string.spoken_description_mode_symbol_shift; 175 break; 176 case KeyboardId.ELEMENT_PHONE: 177 resId = R.string.spoken_description_mode_phone; 178 break; 179 case KeyboardId.ELEMENT_PHONE_SYMBOLS: 180 resId = R.string.spoken_description_mode_phone_shift; 181 break; 182 default: 183 return; 184 } 185 sendWindowStateChanged(resId); 186 } 187 188 /** 189 * Announces that the keyboard has been hidden. 190 */ 191 private void announceKeyboardHidden() { 192 sendWindowStateChanged(R.string.announce_keyboard_hidden); 193 } 194 195 @Override 196 protected void onRegisterHoverKey(final Key key, final MotionEvent event) { 197 final int x = key.getHitBox().centerX(); 198 final int y = key.getHitBox().centerY(); 199 if (DEBUG_HOVER) { 200 Log.d(TAG, "onRegisterHoverKey: key=" + key 201 + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)); 202 } 203 if (mBoundsToIgnoreHoverEvent.contains(x, y)) { 204 // This hover exit event points to the key that should be ignored. 205 // Clear the ignoring region to handle further hover events. 206 mBoundsToIgnoreHoverEvent.setEmpty(); 207 return; 208 } 209 super.onRegisterHoverKey(key, event); 210 } 211 212 @Override 213 protected void onHoverEnterTo(final Key key) { 214 final int x = key.getHitBox().centerX(); 215 final int y = key.getHitBox().centerY(); 216 if (DEBUG_HOVER) { 217 Log.d(TAG, "onHoverEnterTo: key=" + key 218 + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)); 219 } 220 mAccessibilityLongPressTimer.cancelLongPress(); 221 if (mBoundsToIgnoreHoverEvent.contains(x, y)) { 222 return; 223 } 224 // This hover enter event points to the key that isn't in the ignoring region. 225 // Further hover events should be handled. 226 mBoundsToIgnoreHoverEvent.setEmpty(); 227 super.onHoverEnterTo(key); 228 if (key.isLongPressEnabled()) { 229 mAccessibilityLongPressTimer.startLongPress(key); 230 } 231 } 232 233 @Override 234 protected void onHoverExitFrom(final Key key) { 235 final int x = key.getHitBox().centerX(); 236 final int y = key.getHitBox().centerY(); 237 if (DEBUG_HOVER) { 238 Log.d(TAG, "onHoverExitFrom: key=" + key 239 + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)); 240 } 241 mAccessibilityLongPressTimer.cancelLongPress(); 242 super.onHoverExitFrom(key); 243 } 244 245 @Override 246 public void onLongPressed(final Key key) { 247 if (DEBUG_HOVER) { 248 Log.d(TAG, "onLongPressed: key=" + key); 249 } 250 final PointerTracker tracker = PointerTracker.getPointerTracker(HOVER_EVENT_POINTER_ID); 251 final long eventTime = SystemClock.uptimeMillis(); 252 final int x = key.getHitBox().centerX(); 253 final int y = key.getHitBox().centerY(); 254 final MotionEvent downEvent = MotionEvent.obtain( 255 eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */); 256 // Inject a fake down event to {@link PointerTracker} to handle a long press correctly. 257 tracker.processMotionEvent(downEvent, mKeyDetector); 258 // The above fake down event triggers an unnecessary long press timer that should be 259 // canceled. 260 tracker.cancelLongPressTimer(); 261 downEvent.recycle(); 262 // Invoke {@link MainKeyboardView#onLongPress(PointerTracker)} as if a long press timeout 263 // has passed. 264 mKeyboardView.onLongPress(tracker); 265 // If {@link Key#hasNoPanelAutoMoreKeys()} is true (such as "0 +" key on the phone layout) 266 // or a key invokes IME switcher dialog, we should just ignore the next 267 // {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether 268 // {@link PointerTracker} is in operation or not. 269 if (tracker.isInOperation()) { 270 // This long press shows a more keys keyboard and further hover events should be 271 // handled. 272 mBoundsToIgnoreHoverEvent.setEmpty(); 273 return; 274 } 275 // This long press has handled at {@link MainKeyboardView#onLongPress(PointerTracker)}. 276 // We should ignore further hover events on this key. 277 mBoundsToIgnoreHoverEvent.set(key.getHitBox()); 278 if (key.hasNoPanelAutoMoreKey()) { 279 // This long press has registered a code point without showing a more keys keyboard. 280 // We should talk back the code point if possible. 281 final int codePointOfNoPanelAutoMoreKey = key.getMoreKeys()[0].mCode; 282 final String text = KeyCodeDescriptionMapper.getInstance().getDescriptionForCodePoint( 283 mKeyboardView.getContext(), codePointOfNoPanelAutoMoreKey); 284 if (text != null) { 285 sendWindowStateChanged(text); 286 } 287 } 288 } 289} 290