| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.inputmethod.accessibility; |
| |
| import android.content.Context; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.util.SparseIntArray; |
| import android.view.inputmethod.EditorInfo; |
| |
| import com.android.inputmethod.keyboard.Key; |
| import com.android.inputmethod.keyboard.Keyboard; |
| import com.android.inputmethod.keyboard.KeyboardId; |
| import com.android.inputmethod.latin.CollectionUtils; |
| import com.android.inputmethod.latin.Constants; |
| import com.android.inputmethod.latin.R; |
| |
| import java.util.HashMap; |
| |
| public final class KeyCodeDescriptionMapper { |
| private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName(); |
| |
| // The resource ID of the string spoken for obscured keys |
| private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot; |
| |
| private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); |
| |
| // Map of key labels to spoken description resource IDs |
| private final HashMap<CharSequence, Integer> mKeyLabelMap = CollectionUtils.newHashMap(); |
| |
| // Sparse array of spoken description resource IDs indexed by key codes |
| private final SparseIntArray mKeyCodeMap; |
| |
| public static void init() { |
| sInstance.initInternal(); |
| } |
| |
| public static KeyCodeDescriptionMapper getInstance() { |
| return sInstance; |
| } |
| |
| private KeyCodeDescriptionMapper() { |
| mKeyCodeMap = new SparseIntArray(); |
| } |
| |
| private void initInternal() { |
| // Manual label substitutions for key labels with no string resource |
| mKeyLabelMap.put(":-)", R.string.spoken_description_smiley); |
| |
| // Special non-character codes defined in Keyboard |
| mKeyCodeMap.put(Constants.CODE_SPACE, R.string.spoken_description_space); |
| mKeyCodeMap.put(Constants.CODE_DELETE, R.string.spoken_description_delete); |
| mKeyCodeMap.put(Constants.CODE_ENTER, R.string.spoken_description_return); |
| mKeyCodeMap.put(Constants.CODE_SETTINGS, R.string.spoken_description_settings); |
| mKeyCodeMap.put(Constants.CODE_SHIFT, R.string.spoken_description_shift); |
| mKeyCodeMap.put(Constants.CODE_SHORTCUT, R.string.spoken_description_mic); |
| mKeyCodeMap.put(Constants.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol); |
| mKeyCodeMap.put(Constants.CODE_TAB, R.string.spoken_description_tab); |
| mKeyCodeMap.put(Constants.CODE_LANGUAGE_SWITCH, |
| R.string.spoken_description_language_switch); |
| mKeyCodeMap.put(Constants.CODE_ACTION_NEXT, R.string.spoken_description_action_next); |
| mKeyCodeMap.put(Constants.CODE_ACTION_PREVIOUS, |
| R.string.spoken_description_action_previous); |
| } |
| |
| /** |
| * Returns the localized description of the action performed by a specified |
| * key based on the current keyboard state. |
| * <p> |
| * The order of precedence for key descriptions is: |
| * <ol> |
| * <li>Manually-defined based on the key label</li> |
| * <li>Automatic or manually-defined based on the key code</li> |
| * <li>Automatically based on the key label</li> |
| * <li>{code null} for keys with no label or key code defined</li> |
| * </p> |
| * |
| * @param context The package's context. |
| * @param keyboard The keyboard on which the key resides. |
| * @param key The key from which to obtain a description. |
| * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. |
| * @return a character sequence describing the action performed by pressing the key |
| */ |
| public String getDescriptionForKey(final Context context, final Keyboard keyboard, |
| final Key key, final boolean shouldObscure) { |
| final int code = key.mCode; |
| |
| if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { |
| final String description = getDescriptionForSwitchAlphaSymbol(context, keyboard); |
| if (description != null) { |
| return description; |
| } |
| } |
| |
| if (code == Constants.CODE_SHIFT) { |
| return getDescriptionForShiftKey(context, keyboard); |
| } |
| |
| if (code == Constants.CODE_ENTER) { |
| // The following function returns the correct description in all action and |
| // regular enter cases, taking care of all modes. |
| return getDescriptionForActionKey(context, keyboard, key); |
| } |
| |
| if (!TextUtils.isEmpty(key.mLabel)) { |
| final String label = key.mLabel.toString().trim(); |
| |
| // First, attempt to map the label to a pre-defined description. |
| if (mKeyLabelMap.containsKey(label)) { |
| return context.getString(mKeyLabelMap.get(label)); |
| } |
| } |
| |
| // Just attempt to speak the description. |
| if (key.mCode != Constants.CODE_UNSPECIFIED) { |
| return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL |
| * key or {@code null} if there is not a description provided for the |
| * current keyboard context. |
| * |
| * @param context The package's context. |
| * @param keyboard The keyboard on which the key resides. |
| * @return a character sequence describing the action performed by pressing the key |
| */ |
| private String getDescriptionForSwitchAlphaSymbol(final Context context, |
| final Keyboard keyboard) { |
| final KeyboardId keyboardId = keyboard.mId; |
| final int elementId = keyboardId.mElementId; |
| final int resId; |
| |
| switch (elementId) { |
| case KeyboardId.ELEMENT_ALPHABET: |
| case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: |
| case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: |
| case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: |
| case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: |
| resId = R.string.spoken_description_to_symbol; |
| break; |
| case KeyboardId.ELEMENT_SYMBOLS: |
| case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: |
| resId = R.string.spoken_description_to_alpha; |
| break; |
| case KeyboardId.ELEMENT_PHONE: |
| resId = R.string.spoken_description_to_symbol; |
| break; |
| case KeyboardId.ELEMENT_PHONE_SYMBOLS: |
| resId = R.string.spoken_description_to_numeric; |
| break; |
| default: |
| Log.e(TAG, "Missing description for keyboard element ID:" + elementId); |
| return null; |
| } |
| return context.getString(resId); |
| } |
| |
| /** |
| * Returns a context-sensitive description of the "Shift" key. |
| * |
| * @param context The package's context. |
| * @param keyboard The keyboard on which the key resides. |
| * @return A context-sensitive description of the "Shift" key. |
| */ |
| private String getDescriptionForShiftKey(final Context context, final Keyboard keyboard) { |
| final KeyboardId keyboardId = keyboard.mId; |
| final int elementId = keyboardId.mElementId; |
| final int resId; |
| |
| switch (elementId) { |
| case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: |
| case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: |
| resId = R.string.spoken_description_caps_lock; |
| break; |
| case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: |
| case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: |
| case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: |
| resId = R.string.spoken_description_shift_shifted; |
| break; |
| default: |
| resId = R.string.spoken_description_shift; |
| } |
| return context.getString(resId); |
| } |
| |
| /** |
| * Returns a context-sensitive description of the "Enter" action key. |
| * |
| * @param context The package's context. |
| * @param keyboard The keyboard on which the key resides. |
| * @param key The key to describe. |
| * @return Returns a context-sensitive description of the "Enter" action key. |
| */ |
| private String getDescriptionForActionKey(final Context context, final Keyboard keyboard, |
| final Key key) { |
| final KeyboardId keyboardId = keyboard.mId; |
| final int actionId = keyboardId.imeAction(); |
| final int resId; |
| |
| // Always use the label, if available. |
| if (!TextUtils.isEmpty(key.mLabel)) { |
| return key.mLabel.toString().trim(); |
| } |
| |
| // Otherwise, use the action ID. |
| switch (actionId) { |
| case EditorInfo.IME_ACTION_SEARCH: |
| resId = R.string.spoken_description_search; |
| break; |
| case EditorInfo.IME_ACTION_GO: |
| resId = R.string.label_go_key; |
| break; |
| case EditorInfo.IME_ACTION_SEND: |
| resId = R.string.label_send_key; |
| break; |
| case EditorInfo.IME_ACTION_NEXT: |
| resId = R.string.label_next_key; |
| break; |
| case EditorInfo.IME_ACTION_DONE: |
| resId = R.string.label_done_key; |
| break; |
| case EditorInfo.IME_ACTION_PREVIOUS: |
| resId = R.string.label_previous_key; |
| break; |
| default: |
| resId = R.string.spoken_description_return; |
| } |
| return context.getString(resId); |
| } |
| |
| /** |
| * Returns a localized character sequence describing what will happen when |
| * the specified key is pressed based on its key code. |
| * <p> |
| * The order of precedence for key code descriptions is: |
| * <ol> |
| * <li>Manually-defined shift-locked description</li> |
| * <li>Manually-defined shifted description</li> |
| * <li>Manually-defined normal description</li> |
| * <li>Automatic based on the character represented by the key code</li> |
| * <li>Fall-back for undefined or control characters</li> |
| * </ol> |
| * </p> |
| * |
| * @param context The package's context. |
| * @param keyboard The keyboard on which the key resides. |
| * @param key The key from which to obtain a description. |
| * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. |
| * @return a character sequence describing the action performed by pressing the key |
| */ |
| private String getDescriptionForKeyCode(final Context context, final Keyboard keyboard, |
| final Key key, final boolean shouldObscure) { |
| final int code = key.mCode; |
| |
| // If the key description should be obscured, now is the time to do it. |
| final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code); |
| if (shouldObscure && isDefinedNonCtrl) { |
| return context.getString(OBSCURED_KEY_RES_ID); |
| } |
| if (mKeyCodeMap.indexOfKey(code) >= 0) { |
| return context.getString(mKeyCodeMap.get(code)); |
| } |
| if (isDefinedNonCtrl) { |
| return Character.toString((char) code); |
| } |
| if (!TextUtils.isEmpty(key.mLabel)) { |
| return key.mLabel; |
| } |
| return context.getString(R.string.spoken_description_unknown, code); |
| } |
| } |