KeyCodeDescriptionMapper.java revision 02dd837805fec1a0f7b0ddf888b3548f5ac2922b
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    public static void init() {
42        sInstance.initInternal();
43    }
44
45    public static KeyCodeDescriptionMapper getInstance() {
46        return sInstance;
47    }
48
49    private KeyCodeDescriptionMapper() {
50        mKeyLabelMap = new HashMap<CharSequence, Integer>();
51        mKeyCodeMap = new HashMap<Integer, Integer>();
52    }
53
54    private void initInternal() {
55        // Manual label substitutions for key labels with no string resource
56        mKeyLabelMap.put(":-)", R.string.spoken_description_smiley);
57
58        // Symbols that most TTS engines can't speak
59        mKeyCodeMap.put((int) ' ', R.string.spoken_description_space);
60
61        // Special non-character codes defined in Keyboard
62        mKeyCodeMap.put(Keyboard.CODE_DELETE, R.string.spoken_description_delete);
63        mKeyCodeMap.put(Keyboard.CODE_ENTER, R.string.spoken_description_return);
64        mKeyCodeMap.put(Keyboard.CODE_SETTINGS, R.string.spoken_description_settings);
65        mKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift);
66        mKeyCodeMap.put(Keyboard.CODE_CAPSLOCK, R.string.spoken_description_caps_lock);
67        mKeyCodeMap.put(Keyboard.CODE_SHORTCUT, R.string.spoken_description_mic);
68        mKeyCodeMap.put(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol);
69        mKeyCodeMap.put(Keyboard.CODE_TAB, R.string.spoken_description_tab);
70    }
71
72    /**
73     * Returns the localized description of the action performed by a specified
74     * key based on the current keyboard state.
75     * <p>
76     * The order of precedence for key descriptions is:
77     * <ol>
78     * <li>Manually-defined based on the key label</li>
79     * <li>Automatic or manually-defined based on the key code</li>
80     * <li>Automatically based on the key label</li>
81     * <li>{code null} for keys with no label or key code defined</li>
82     * </p>
83     *
84     * @param context The package's context.
85     * @param keyboard The keyboard on which the key resides.
86     * @param key The key from which to obtain a description.
87     * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured.
88     * @return a character sequence describing the action performed by pressing
89     *         the key
90     */
91    public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key,
92            boolean shouldObscure) {
93        final int code = key.mCode;
94
95        if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
96            final CharSequence description = getDescriptionForSwitchAlphaSymbol(context, keyboard);
97            if (description != null)
98                return description;
99        }
100
101        if (code == Keyboard.CODE_SHIFT) {
102            return getDescriptionForShiftKey(context, keyboard);
103        }
104
105        if (!TextUtils.isEmpty(key.mLabel)) {
106            final String label = key.mLabel.toString().trim();
107
108            // First, attempt to map the label to a pre-defined description.
109            if (mKeyLabelMap.containsKey(label)) {
110                return context.getString(mKeyLabelMap.get(label));
111            }
112        }
113
114        // Just attempt to speak the description.
115        if (key.mCode != Keyboard.CODE_UNSPECIFIED) {
116            return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
117        }
118
119        return null;
120    }
121
122    /**
123     * Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL
124     * key or {@code null} if there is not a description provided for the
125     * current keyboard context.
126     *
127     * @param context The package's context.
128     * @param keyboard The keyboard on which the key resides.
129     * @return a character sequence describing the action performed by pressing
130     *         the key
131     */
132    private CharSequence getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) {
133        final KeyboardId id = keyboard.mId;
134
135        if (id.isAlphabetKeyboard()) {
136            return context.getString(R.string.spoken_description_to_symbol);
137        } else if (id.isSymbolsKeyboard()) {
138            return context.getString(R.string.spoken_description_to_alpha);
139        } else if (id.isPhoneShiftKeyboard()) {
140            return context.getString(R.string.spoken_description_to_numeric);
141        } else if (id.isPhoneKeyboard()) {
142            return context.getString(R.string.spoken_description_to_symbol);
143        } else {
144            return null;
145        }
146    }
147
148    /**
149     * Returns a context-sensitive description of the "Shift" key.
150     *
151     * @param context The package's context.
152     * @param keyboard The keyboard on which the key resides.
153     * @return A context-sensitive description of the "Shift" key.
154     */
155    private CharSequence getDescriptionForShiftKey(Context context, Keyboard keyboard) {
156        final int resId;
157
158        if (keyboard.isShiftLocked()) {
159            resId = R.string.spoken_description_caps_lock;
160        } else if (keyboard.isShiftedOrShiftLocked()) {
161            resId = R.string.spoken_description_shift_shifted;
162        } else {
163            resId = R.string.spoken_description_shift;
164        }
165
166        return context.getString(resId);
167    }
168
169    /**
170     * Returns a localized character sequence describing what will happen when
171     * the specified key is pressed based on its key code.
172     * <p>
173     * The order of precedence for key code descriptions is:
174     * <ol>
175     * <li>Manually-defined shift-locked description</li>
176     * <li>Manually-defined shifted description</li>
177     * <li>Manually-defined normal description</li>
178     * <li>Automatic based on the character represented by the key code</li>
179     * <li>Fall-back for undefined or control characters</li>
180     * </ol>
181     * </p>
182     *
183     * @param context The package's context.
184     * @param keyboard The keyboard on which the key resides.
185     * @param key The key from which to obtain a description.
186     * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured.
187     * @return a character sequence describing the action performed by pressing
188     *         the key
189     */
190    private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key,
191            boolean shouldObscure) {
192        final int code = key.mCode;
193
194        // If the key description should be obscured, now is the time to do it.
195        final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code);
196        if (shouldObscure && isDefinedNonCtrl) {
197            return context.getString(OBSCURED_KEY_RES_ID);
198        }
199
200        if (mKeyCodeMap.containsKey(code)) {
201            return context.getString(mKeyCodeMap.get(code));
202        } else if (isDefinedNonCtrl) {
203            return Character.toString((char) code);
204        } else {
205            return context.getString(R.string.spoken_description_unknown, code);
206        }
207    }
208}
209