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