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