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