KeyCodeDescriptionMapper.java revision f2eba97cc09c86f9a84b61cccf3f233e1fb85a6c
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.text.TextUtils; 21import android.util.Log; 22 23import com.android.inputmethod.keyboard.Key; 24import com.android.inputmethod.keyboard.Keyboard; 25import com.android.inputmethod.keyboard.KeyboardId; 26import com.android.inputmethod.latin.R; 27 28import java.util.HashMap; 29 30public class KeyCodeDescriptionMapper { 31 private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName(); 32 33 // The resource ID of the string spoken for obscured keys 34 private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot; 35 36 private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); 37 38 // Map of key labels to spoken description resource IDs 39 private final HashMap<CharSequence, Integer> mKeyLabelMap; 40 41 // Map of key codes to spoken description resource IDs 42 private final HashMap<Integer, Integer> mKeyCodeMap; 43 44 public static void init() { 45 sInstance.initInternal(); 46 } 47 48 public static KeyCodeDescriptionMapper getInstance() { 49 return sInstance; 50 } 51 52 private KeyCodeDescriptionMapper() { 53 mKeyLabelMap = new HashMap<CharSequence, Integer>(); 54 mKeyCodeMap = new HashMap<Integer, Integer>(); 55 } 56 57 private void initInternal() { 58 // Manual label substitutions for key labels with no string resource 59 mKeyLabelMap.put(":-)", R.string.spoken_description_smiley); 60 61 // Symbols that most TTS engines can't speak 62 mKeyCodeMap.put((int) ' ', R.string.spoken_description_space); 63 64 // Special non-character codes defined in Keyboard 65 mKeyCodeMap.put(Keyboard.CODE_DELETE, R.string.spoken_description_delete); 66 mKeyCodeMap.put(Keyboard.CODE_ENTER, R.string.spoken_description_return); 67 mKeyCodeMap.put(Keyboard.CODE_SETTINGS, R.string.spoken_description_settings); 68 mKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift); 69 mKeyCodeMap.put(Keyboard.CODE_SHORTCUT, R.string.spoken_description_mic); 70 mKeyCodeMap.put(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol); 71 mKeyCodeMap.put(Keyboard.CODE_TAB, R.string.spoken_description_tab); 72 } 73 74 /** 75 * Returns the localized description of the action performed by a specified 76 * key based on the current keyboard state. 77 * <p> 78 * The order of precedence for key descriptions is: 79 * <ol> 80 * <li>Manually-defined based on the key label</li> 81 * <li>Automatic or manually-defined based on the key code</li> 82 * <li>Automatically based on the key label</li> 83 * <li>{code null} for keys with no label or key code defined</li> 84 * </p> 85 * 86 * @param context The package's context. 87 * @param keyboard The keyboard on which the key resides. 88 * @param key The key from which to obtain a description. 89 * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. 90 * @return a character sequence describing the action performed by pressing 91 * the key 92 */ 93 public String getDescriptionForKey(Context context, Keyboard keyboard, Key key, 94 boolean shouldObscure) { 95 final int code = key.mCode; 96 97 if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 98 final String description = getDescriptionForSwitchAlphaSymbol(context, keyboard); 99 if (description != null) 100 return description; 101 } 102 103 if (code == Keyboard.CODE_SHIFT) { 104 return getDescriptionForShiftKey(context, keyboard); 105 } 106 107 if (!TextUtils.isEmpty(key.mLabel)) { 108 final String label = key.mLabel.toString().trim(); 109 110 // First, attempt to map the label to a pre-defined description. 111 if (mKeyLabelMap.containsKey(label)) { 112 return context.getString(mKeyLabelMap.get(label)); 113 } 114 115 // Otherwise, return the label. 116 return key.mLabel; 117 } 118 119 // Just attempt to speak the description. 120 if (key.mCode != Keyboard.CODE_UNSPECIFIED) { 121 return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); 122 } 123 124 return null; 125 } 126 127 /** 128 * Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL 129 * key or {@code null} if there is not a description provided for the 130 * current keyboard context. 131 * 132 * @param context The package's context. 133 * @param keyboard The keyboard on which the key resides. 134 * @return a character sequence describing the action performed by pressing 135 * the key 136 */ 137 private String getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) { 138 final KeyboardId keyboardId = keyboard.mId; 139 final int elementId = keyboardId.mElementId; 140 final int resId; 141 142 switch (elementId) { 143 case KeyboardId.ELEMENT_ALPHABET: 144 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 145 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 146 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 147 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 148 resId = R.string.spoken_description_to_symbol; 149 break; 150 case KeyboardId.ELEMENT_SYMBOLS: 151 case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: 152 resId = R.string.spoken_description_to_alpha; 153 break; 154 case KeyboardId.ELEMENT_PHONE: 155 resId = R.string.spoken_description_to_symbol; 156 break; 157 case KeyboardId.ELEMENT_PHONE_SYMBOLS: 158 resId = R.string.spoken_description_to_numeric; 159 break; 160 default: 161 Log.e(TAG, "Missing description for keyboard element ID:" + elementId); 162 return null; 163 } 164 165 return context.getString(resId); 166 } 167 168 /** 169 * Returns a context-sensitive description of the "Shift" key. 170 * 171 * @param context The package's context. 172 * @param keyboard The keyboard on which the key resides. 173 * @return A context-sensitive description of the "Shift" key. 174 */ 175 private String getDescriptionForShiftKey(Context context, Keyboard keyboard) { 176 final KeyboardId keyboardId = keyboard.mId; 177 final int elementId = keyboardId.mElementId; 178 final int resId; 179 180 switch (elementId) { 181 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 182 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 183 resId = R.string.spoken_description_caps_lock; 184 break; 185 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 186 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 187 case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: 188 resId = R.string.spoken_description_shift_shifted; 189 break; 190 default: 191 resId = R.string.spoken_description_shift; 192 } 193 194 return context.getString(resId); 195 } 196 197 /** 198 * Returns a localized character sequence describing what will happen when 199 * the specified key is pressed based on its key code. 200 * <p> 201 * The order of precedence for key code descriptions is: 202 * <ol> 203 * <li>Manually-defined shift-locked description</li> 204 * <li>Manually-defined shifted description</li> 205 * <li>Manually-defined normal description</li> 206 * <li>Automatic based on the character represented by the key code</li> 207 * <li>Fall-back for undefined or control characters</li> 208 * </ol> 209 * </p> 210 * 211 * @param context The package's context. 212 * @param keyboard The keyboard on which the key resides. 213 * @param key The key from which to obtain a description. 214 * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. 215 * @return a character sequence describing the action performed by pressing 216 * the key 217 */ 218 private String getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key, 219 boolean shouldObscure) { 220 final int code = key.mCode; 221 222 // If the key description should be obscured, now is the time to do it. 223 final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code); 224 if (shouldObscure && isDefinedNonCtrl) { 225 return context.getString(OBSCURED_KEY_RES_ID); 226 } 227 228 if (mKeyCodeMap.containsKey(code)) { 229 return context.getString(mKeyCodeMap.get(code)); 230 } else if (isDefinedNonCtrl) { 231 return Character.toString((char) code); 232 } else { 233 return context.getString(R.string.spoken_description_unknown, code); 234 } 235 } 236} 237