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