/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.inputmethod.accessibility; import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.sugree.inputmethod.latin.R; import java.util.HashMap; public class KeyCodeDescriptionMapper { // The resource ID of the string spoken for obscured keys private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot; private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); // Map of key labels to spoken description resource IDs private final HashMap<CharSequence, Integer> mKeyLabelMap; // Map of key codes to spoken description resource IDs private final HashMap<Integer, Integer> mKeyCodeMap; // Map of shifted key codes to spoken description resource IDs private final HashMap<Integer, Integer> mShiftedKeyCodeMap; // Map of shift-locked key codes to spoken description resource IDs private final HashMap<Integer, Integer> mShiftLockedKeyCodeMap; public static void init(Context context, SharedPreferences prefs) { sInstance.initInternal(context, prefs); } public static KeyCodeDescriptionMapper getInstance() { return sInstance; } private KeyCodeDescriptionMapper() { mKeyLabelMap = new HashMap<CharSequence, Integer>(); mKeyCodeMap = new HashMap<Integer, Integer>(); mShiftedKeyCodeMap = new HashMap<Integer, Integer>(); mShiftLockedKeyCodeMap = new HashMap<Integer, Integer>(); } private void initInternal(Context context, SharedPreferences prefs) { // Manual label substitutions for key labels with no string resource mKeyLabelMap.put(":-)", R.string.spoken_description_smiley); // Symbols that most TTS engines can't speak mKeyCodeMap.put((int) '.', R.string.spoken_description_period); mKeyCodeMap.put((int) ',', R.string.spoken_description_comma); mKeyCodeMap.put((int) '(', R.string.spoken_description_left_parenthesis); mKeyCodeMap.put((int) ')', R.string.spoken_description_right_parenthesis); mKeyCodeMap.put((int) ':', R.string.spoken_description_colon); mKeyCodeMap.put((int) ';', R.string.spoken_description_semicolon); mKeyCodeMap.put((int) '!', R.string.spoken_description_exclamation_mark); mKeyCodeMap.put((int) '?', R.string.spoken_description_question_mark); mKeyCodeMap.put((int) '\"', R.string.spoken_description_double_quote); mKeyCodeMap.put((int) '\'', R.string.spoken_description_single_quote); mKeyCodeMap.put((int) '*', R.string.spoken_description_star); mKeyCodeMap.put((int) '#', R.string.spoken_description_pound); mKeyCodeMap.put((int) ' ', R.string.spoken_description_space); // Non-ASCII symbols (must use escape codes!) mKeyCodeMap.put((int) '\u2022', R.string.spoken_description_dot); mKeyCodeMap.put((int) '\u221A', R.string.spoken_description_square_root); mKeyCodeMap.put((int) '\u03C0', R.string.spoken_description_pi); mKeyCodeMap.put((int) '\u0394', R.string.spoken_description_delta); mKeyCodeMap.put((int) '\u2122', R.string.spoken_description_trademark); mKeyCodeMap.put((int) '\u2105', R.string.spoken_description_care_of); mKeyCodeMap.put((int) '\u2026', R.string.spoken_description_ellipsis); mKeyCodeMap.put((int) '\u201E', R.string.spoken_description_low_double_quote); mKeyCodeMap.put((int) '\uFF0A', R.string.spoken_description_star); // Special non-character codes defined in Keyboard mKeyCodeMap.put(Keyboard.CODE_DELETE, R.string.spoken_description_delete); mKeyCodeMap.put(Keyboard.CODE_ENTER, R.string.spoken_description_return); mKeyCodeMap.put(Keyboard.CODE_SETTINGS, R.string.spoken_description_settings); mKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift); mKeyCodeMap.put(Keyboard.CODE_SHORTCUT, R.string.spoken_description_mic); mKeyCodeMap.put(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol); mKeyCodeMap.put(Keyboard.CODE_TAB, R.string.spoken_description_tab); // Shifted versions of non-character codes defined in Keyboard mShiftedKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift_shifted); // Shift-locked versions of non-character codes defined in Keyboard mShiftLockedKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_caps_lock); } /** * Returns the localized description of the action performed by a specified * key based on the current keyboard state. * <p> * The order of precedence for key descriptions is: * <ol> * <li>Manually-defined based on the key label</li> * <li>Automatic or manually-defined based on the key code</li> * <li>Automatically based on the key label</li> * <li>{code null} for keys with no label or key code defined</li> * </p> * * @param context The package's context. * @param keyboard The keyboard on which the key resides. * @param key The key from which to obtain a description. * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. * @return a character sequence describing the action performed by pressing * the key */ public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key, boolean shouldObscure) { if (key.mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { final CharSequence description = getDescriptionForSwitchAlphaSymbol(context, keyboard); if (description != null) return description; } if (!TextUtils.isEmpty(key.mLabel)) { final String label = key.mLabel.toString().trim(); if (mKeyLabelMap.containsKey(label)) { return context.getString(mKeyLabelMap.get(label)); } else if (label.length() == 1 || (keyboard.isManualTemporaryUpperCase() && !TextUtils .isEmpty(key.mHintLabel))) { return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); } else { return label; } } else if (key.mCode != Keyboard.CODE_DUMMY) { return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); } return null; } /** * Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL * key or {@code null} if there is not a description provided for the * current keyboard context. * * @param context The package's context. * @param keyboard The keyboard on which the key resides. * @return a character sequence describing the action performed by pressing * the key */ private CharSequence getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) { final KeyboardId id = keyboard.mId; if (id.isAlphabetKeyboard()) { return context.getString(R.string.spoken_description_to_symbol); } else if (id.isSymbolsKeyboard()) { return context.getString(R.string.spoken_description_to_alpha); } else if (id.isPhoneShiftKeyboard()) { return context.getString(R.string.spoken_description_to_numeric); } else if (id.isPhoneKeyboard()) { return context.getString(R.string.spoken_description_to_symbol); } else { return null; } } /** * Returns the keycode for the specified key given the current keyboard * state. * * @param keyboard The keyboard on which the key resides. * @param key The key from which to obtain a key code. * @return the key code for the specified key */ private int getCorrectKeyCode(Keyboard keyboard, Key key) { if (keyboard.isManualTemporaryUpperCase() && !TextUtils.isEmpty(key.mHintLabel)) { return key.mHintLabel.charAt(0); } else { return key.mCode; } } /** * Returns a localized character sequence describing what will happen when * the specified key is pressed based on its key code. * <p> * The order of precedence for key code descriptions is: * <ol> * <li>Manually-defined shift-locked description</li> * <li>Manually-defined shifted description</li> * <li>Manually-defined normal description</li> * <li>Automatic based on the character represented by the key code</li> * <li>Fall-back for undefined or control characters</li> * </ol> * </p> * * @param context The package's context. * @param keyboard The keyboard on which the key resides. * @param key The key from which to obtain a description. * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. * @return a character sequence describing the action performed by pressing * the key */ private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key, boolean shouldObscure) { final int code = getCorrectKeyCode(keyboard, key); if (keyboard.isShiftLocked() && mShiftLockedKeyCodeMap.containsKey(code)) { return context.getString(mShiftLockedKeyCodeMap.get(code)); } else if (keyboard.isShiftedOrShiftLocked() && mShiftedKeyCodeMap.containsKey(code)) { return context.getString(mShiftedKeyCodeMap.get(code)); } // If the key description should be obscured, now is the time to do it. final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code); if (shouldObscure && isDefinedNonCtrl) { return context.getString(OBSCURED_KEY_RES_ID); } if (mKeyCodeMap.containsKey(code)) { return context.getString(mKeyCodeMap.get(code)); } else if (isDefinedNonCtrl) { return Character.toString((char) code); } else { return context.getString(R.string.spoken_description_unknown, code); } } }