KeyCodeDescriptionMapper.java revision e87fd4d2826734a931d7d6f019ee36212b5b060a
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.content.SharedPreferences; 21import android.text.TextUtils; 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 // The resource ID of the string spoken for obscured keys 32 private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot; 33 34 private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); 35 36 // Map of key labels to spoken description resource IDs 37 private final HashMap<CharSequence, Integer> mKeyLabelMap; 38 39 // Map of key codes to spoken description resource IDs 40 private final HashMap<Integer, Integer> mKeyCodeMap; 41 42 // Map of shifted key codes to spoken description resource IDs 43 private final HashMap<Integer, Integer> mShiftedKeyCodeMap; 44 45 // Map of shift-locked key codes to spoken description resource IDs 46 private final HashMap<Integer, Integer> mShiftLockedKeyCodeMap; 47 48 public static void init(Context context, SharedPreferences prefs) { 49 sInstance.initInternal(context, prefs); 50 } 51 52 public static KeyCodeDescriptionMapper getInstance() { 53 return sInstance; 54 } 55 56 private KeyCodeDescriptionMapper() { 57 mKeyLabelMap = new HashMap<CharSequence, Integer>(); 58 mKeyCodeMap = new HashMap<Integer, Integer>(); 59 mShiftedKeyCodeMap = new HashMap<Integer, Integer>(); 60 mShiftLockedKeyCodeMap = new HashMap<Integer, Integer>(); 61 } 62 63 private void initInternal(Context context, SharedPreferences prefs) { 64 // Manual label substitutions for key labels with no string resource 65 mKeyLabelMap.put(":-)", R.string.spoken_description_smiley); 66 67 // Symbols that most TTS engines can't speak 68 mKeyCodeMap.put((int) '.', R.string.spoken_description_period); 69 mKeyCodeMap.put((int) ',', R.string.spoken_description_comma); 70 mKeyCodeMap.put((int) '(', R.string.spoken_description_left_parenthesis); 71 mKeyCodeMap.put((int) ')', R.string.spoken_description_right_parenthesis); 72 mKeyCodeMap.put((int) ':', R.string.spoken_description_colon); 73 mKeyCodeMap.put((int) ';', R.string.spoken_description_semicolon); 74 mKeyCodeMap.put((int) '!', R.string.spoken_description_exclamation_mark); 75 mKeyCodeMap.put((int) '?', R.string.spoken_description_question_mark); 76 mKeyCodeMap.put((int) '\"', R.string.spoken_description_double_quote); 77 mKeyCodeMap.put((int) '\'', R.string.spoken_description_single_quote); 78 mKeyCodeMap.put((int) '*', R.string.spoken_description_star); 79 mKeyCodeMap.put((int) '#', R.string.spoken_description_pound); 80 mKeyCodeMap.put((int) ' ', R.string.spoken_description_space); 81 82 // Non-ASCII symbols (must use escape codes!) 83 mKeyCodeMap.put((int) '\u2022', R.string.spoken_description_dot); 84 mKeyCodeMap.put((int) '\u221A', R.string.spoken_description_square_root); 85 mKeyCodeMap.put((int) '\u03C0', R.string.spoken_description_pi); 86 mKeyCodeMap.put((int) '\u0394', R.string.spoken_description_delta); 87 mKeyCodeMap.put((int) '\u2122', R.string.spoken_description_trademark); 88 mKeyCodeMap.put((int) '\u2105', R.string.spoken_description_care_of); 89 mKeyCodeMap.put((int) '\u2026', R.string.spoken_description_ellipsis); 90 mKeyCodeMap.put((int) '\u201E', R.string.spoken_description_low_double_quote); 91 mKeyCodeMap.put((int) '\uFF0A', R.string.spoken_description_star); 92 93 // Special non-character codes defined in Keyboard 94 mKeyCodeMap.put(Keyboard.CODE_DELETE, R.string.spoken_description_delete); 95 mKeyCodeMap.put(Keyboard.CODE_ENTER, R.string.spoken_description_return); 96 mKeyCodeMap.put(Keyboard.CODE_SETTINGS, R.string.spoken_description_settings); 97 mKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift); 98 mKeyCodeMap.put(Keyboard.CODE_SHORTCUT, R.string.spoken_description_mic); 99 mKeyCodeMap.put(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol); 100 mKeyCodeMap.put(Keyboard.CODE_TAB, R.string.spoken_description_tab); 101 102 // Shifted versions of non-character codes defined in Keyboard 103 mShiftedKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift_shifted); 104 105 // Shift-locked versions of non-character codes defined in Keyboard 106 mShiftLockedKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_caps_lock); 107 } 108 109 /** 110 * Returns the localized description of the action performed by a specified 111 * key based on the current keyboard state. 112 * <p> 113 * The order of precedence for key descriptions is: 114 * <ol> 115 * <li>Manually-defined based on the key label</li> 116 * <li>Automatic or manually-defined based on the key code</li> 117 * <li>Automatically based on the key label</li> 118 * <li>{code null} for keys with no label or key code defined</li> 119 * </p> 120 * 121 * @param context The package's context. 122 * @param keyboard The keyboard on which the key resides. 123 * @param key The key from which to obtain a description. 124 * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. 125 * @return a character sequence describing the action performed by pressing 126 * the key 127 */ 128 public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key, 129 boolean shouldObscure) { 130 if (key.mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 131 final CharSequence description = getDescriptionForSwitchAlphaSymbol(context, keyboard); 132 if (description != null) 133 return description; 134 } 135 136 if (!TextUtils.isEmpty(key.mLabel)) { 137 final String label = key.mLabel.toString().trim(); 138 139 // First, attempt to map the label to a pre-defined description. 140 if (mKeyLabelMap.containsKey(label)) { 141 return context.getString(mKeyLabelMap.get(label)); 142 } 143 } 144 145 // Just attempt to speak the description. 146 if (key.mCode != Keyboard.CODE_DUMMY) { 147 return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); 148 } 149 150 return null; 151 } 152 153 /** 154 * Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL 155 * key or {@code null} if there is not a description provided for the 156 * current keyboard context. 157 * 158 * @param context The package's context. 159 * @param keyboard The keyboard on which the key resides. 160 * @return a character sequence describing the action performed by pressing 161 * the key 162 */ 163 private CharSequence getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) { 164 final KeyboardId id = keyboard.mId; 165 166 if (id.isAlphabetKeyboard()) { 167 return context.getString(R.string.spoken_description_to_symbol); 168 } else if (id.isSymbolsKeyboard()) { 169 return context.getString(R.string.spoken_description_to_alpha); 170 } else if (id.isPhoneShiftKeyboard()) { 171 return context.getString(R.string.spoken_description_to_numeric); 172 } else if (id.isPhoneKeyboard()) { 173 return context.getString(R.string.spoken_description_to_symbol); 174 } else { 175 return null; 176 } 177 } 178 179 /** 180 * Returns the keycode for the specified key given the current keyboard 181 * state. 182 * 183 * @param keyboard The keyboard on which the key resides. 184 * @param key The key from which to obtain a key code. 185 * @return the key code for the specified key 186 */ 187 private int getCorrectKeyCode(Keyboard keyboard, Key key) { 188 // If keyboard is in manual temporary upper case state and key has 189 // manual temporary uppercase letter as key hint letter, alternate 190 // character code should be sent. 191 if (keyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter() 192 && !TextUtils.isEmpty(key.mHintLabel)) { 193 return key.mHintLabel.charAt(0); 194 } 195 return key.mCode; 196 } 197 198 /** 199 * Returns a localized character sequence describing what will happen when 200 * the specified key is pressed based on its key code. 201 * <p> 202 * The order of precedence for key code descriptions is: 203 * <ol> 204 * <li>Manually-defined shift-locked description</li> 205 * <li>Manually-defined shifted description</li> 206 * <li>Manually-defined normal description</li> 207 * <li>Automatic based on the character represented by the key code</li> 208 * <li>Fall-back for undefined or control characters</li> 209 * </ol> 210 * </p> 211 * 212 * @param context The package's context. 213 * @param keyboard The keyboard on which the key resides. 214 * @param key The key from which to obtain a description. 215 * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. 216 * @return a character sequence describing the action performed by pressing 217 * the key 218 */ 219 private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key, 220 boolean shouldObscure) { 221 final int code = getCorrectKeyCode(keyboard, key); 222 223 if (keyboard.isShiftLocked() && mShiftLockedKeyCodeMap.containsKey(code)) { 224 return context.getString(mShiftLockedKeyCodeMap.get(code)); 225 } else if (keyboard.isShiftedOrShiftLocked() && mShiftedKeyCodeMap.containsKey(code)) { 226 return context.getString(mShiftedKeyCodeMap.get(code)); 227 } 228 229 // If the key description should be obscured, now is the time to do it. 230 final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code); 231 if (shouldObscure && isDefinedNonCtrl) { 232 return context.getString(OBSCURED_KEY_RES_ID); 233 } 234 235 if (mKeyCodeMap.containsKey(code)) { 236 return context.getString(mKeyCodeMap.get(code)); 237 } else if (isDefinedNonCtrl) { 238 return Character.toString((char) code); 239 } else { 240 return context.getString(R.string.spoken_description_unknown, code); 241 } 242 } 243} 244