• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.CollectionUtils;
29 import com.android.inputmethod.latin.Constants;
30 import com.android.inputmethod.latin.R;
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 
init()48     public static void init() {
49         sInstance.initInternal();
50     }
51 
getInstance()52     public static KeyCodeDescriptionMapper getInstance() {
53         return sInstance;
54     }
55 
KeyCodeDescriptionMapper()56     private KeyCodeDescriptionMapper() {
57         mKeyCodeMap = new SparseIntArray();
58     }
59 
initInternal()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      */
getDescriptionForKey(final Context context, final Keyboard keyboard, final Key key, final boolean shouldObscure)98     public String getDescriptionForKey(final Context context, final Keyboard keyboard,
99             final Key key, final boolean shouldObscure) {
100         final int code = key.mCode;
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.mLabel)) {
120             final String label = key.mLabel.toString().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.mCode != 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      */
getDescriptionForSwitchAlphaSymbol(final Context context, final Keyboard keyboard)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      */
getDescriptionForShiftKey(final Context context, final Keyboard keyboard)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      */
getDescriptionForActionKey(final Context context, final Keyboard keyboard, final Key key)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.mLabel)) {
219             return key.mLabel.toString().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      */
getDescriptionForKeyCode(final Context context, final Keyboard keyboard, final Key key, final boolean shouldObscure)268     private String getDescriptionForKeyCode(final Context context, final Keyboard keyboard,
269             final Key key, final boolean shouldObscure) {
270         final int code = key.mCode;
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.mLabel)) {
284             return key.mLabel;
285         }
286         return context.getString(R.string.spoken_description_unknown, code);
287     }
288 }
289