Home | History | Annotate | Download | only in accessibility
      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 
     17 package com.android.inputmethod.accessibility;
     18 
     19 import android.content.Context;
     20 import android.content.SharedPreferences;
     21 import android.text.TextUtils;
     22 
     23 import com.android.inputmethod.keyboard.Key;
     24 import com.android.inputmethod.keyboard.Keyboard;
     25 import com.android.inputmethod.keyboard.KeyboardId;
     26 import com.android.inputmethod.latin.R;
     27 
     28 import java.util.HashMap;
     29 
     30 public 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             if (mKeyLabelMap.containsKey(label)) {
    140                 return context.getString(mKeyLabelMap.get(label));
    141             } else if (label.length() == 1
    142                     || (keyboard.isManualTemporaryUpperCase() && !TextUtils
    143                             .isEmpty(key.mHintLabel))) {
    144                 return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
    145             } else {
    146                 return label;
    147             }
    148         } else if (key.mCode != Keyboard.CODE_DUMMY) {
    149             return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
    150         }
    151 
    152         return null;
    153     }
    154 
    155     /**
    156      * Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL
    157      * key or {@code null} if there is not a description provided for the
    158      * current keyboard context.
    159      *
    160      * @param context The package's context.
    161      * @param keyboard The keyboard on which the key resides.
    162      * @return a character sequence describing the action performed by pressing
    163      *         the key
    164      */
    165     private CharSequence getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) {
    166         final KeyboardId id = keyboard.mId;
    167 
    168         if (id.isAlphabetKeyboard()) {
    169             return context.getString(R.string.spoken_description_to_symbol);
    170         } else if (id.isSymbolsKeyboard()) {
    171             return context.getString(R.string.spoken_description_to_alpha);
    172         } else if (id.isPhoneShiftKeyboard()) {
    173             return context.getString(R.string.spoken_description_to_numeric);
    174         } else if (id.isPhoneKeyboard()) {
    175             return context.getString(R.string.spoken_description_to_symbol);
    176         } else {
    177             return null;
    178         }
    179     }
    180 
    181     /**
    182      * Returns the keycode for the specified key given the current keyboard
    183      * state.
    184      *
    185      * @param keyboard The keyboard on which the key resides.
    186      * @param key The key from which to obtain a key code.
    187      * @return the key code for the specified key
    188      */
    189     private int getCorrectKeyCode(Keyboard keyboard, Key key) {
    190         if (keyboard.isManualTemporaryUpperCase() && !TextUtils.isEmpty(key.mHintLabel)) {
    191             return key.mHintLabel.charAt(0);
    192         } else {
    193             return key.mCode;
    194         }
    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 CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key,
    219             boolean shouldObscure) {
    220         final int code = getCorrectKeyCode(keyboard, key);
    221 
    222         if (keyboard.isShiftLocked() && mShiftLockedKeyCodeMap.containsKey(code)) {
    223             return context.getString(mShiftLockedKeyCodeMap.get(code));
    224         } else if (keyboard.isShiftedOrShiftLocked() && mShiftedKeyCodeMap.containsKey(code)) {
    225             return context.getString(mShiftedKeyCodeMap.get(code));
    226         }
    227 
    228         // If the key description should be obscured, now is the time to do it.
    229         final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code);
    230         if (shouldObscure && isDefinedNonCtrl) {
    231             return context.getString(OBSCURED_KEY_RES_ID);
    232         }
    233 
    234         if (mKeyCodeMap.containsKey(code)) {
    235             return context.getString(mKeyCodeMap.get(code));
    236         } else if (isDefinedNonCtrl) {
    237             return Character.toString((char) code);
    238         } else {
    239             return context.getString(R.string.spoken_description_unknown, code);
    240         }
    241     }
    242 }
    243