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