/* * 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.content.res.Resources; 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.Constants; import com.android.inputmethod.latin.R; import java.util.Locale; final class KeyCodeDescriptionMapper { private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName(); private static final String SPOKEN_LETTER_RESOURCE_NAME_FORMAT = "spoken_accented_letter_%04X"; private static final String SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT = "spoken_symbol_%04X"; private static final String SPOKEN_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X"; // 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 final KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); public static KeyCodeDescriptionMapper getInstance() { return sInstance; } // Sparse array of spoken description resource IDs indexed by key codes private final SparseIntArray mKeyCodeMap = new SparseIntArray(); private KeyCodeDescriptionMapper() { // 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); mKeyCodeMap.put(Constants.CODE_EMOJI, R.string.spoken_description_emoji); // Because the upper-case and lower-case mappings of the following letters is depending on // the locale, the upper case descriptions should be defined here. The lower case // descriptions are handled in {@link #getSpokenLetterDescriptionId(Context,int)}. // U+0049: "I" LATIN CAPITAL LETTER I // U+0069: "i" LATIN SMALL LETTER I // U+0130: "İ" LATIN CAPITAL LETTER I WITH DOT ABOVE // U+0131: "ı" LATIN SMALL LETTER DOTLESS I mKeyCodeMap.put(0x0049, R.string.spoken_letter_0049); mKeyCodeMap.put(0x0130, R.string.spoken_letter_0130); } /** * Returns the localized description of the action performed by a specified * key based on the current keyboard state. *
* The order of precedence for key descriptions is: *
* The order of precedence for key code descriptions is: *
code
may be a base (non-accented) letter.
final String unsupportedSymbol = getSpokenSymbolDescription(context, code);
if (unsupportedSymbol != null) {
return unsupportedSymbol;
}
final String emojiDescription = getSpokenEmojiDescription(context, code);
if (emojiDescription != null) {
return emojiDescription;
}
if (isDefinedNonCtrl) {
return Character.toString((char) code);
}
if (!TextUtils.isEmpty(key.getLabel())) {
return key.getLabel();
}
return context.getString(R.string.spoken_description_unknown, code);
}
// TODO: Remove this method once TTS supports those accented letters' verbalization.
private String getSpokenAccentedLetterDescription(final Context context, final int code) {
final boolean isUpperCase = Character.isUpperCase(code);
final int baseCode = isUpperCase ? Character.toLowerCase(code) : code;
final int baseIndex = mKeyCodeMap.indexOfKey(baseCode);
final int resId = (baseIndex >= 0) ? mKeyCodeMap.valueAt(baseIndex)
: getSpokenDescriptionId(context, baseCode, SPOKEN_LETTER_RESOURCE_NAME_FORMAT);
if (resId == 0) {
return null;
}
final String spokenText = context.getString(resId);
return isUpperCase ? context.getString(R.string.spoken_description_upper_case, spokenText)
: spokenText;
}
// TODO: Remove this method once TTS supports those symbols' verbalization.
private String getSpokenSymbolDescription(final Context context, final int code) {
final int resId = getSpokenDescriptionId(context, code, SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT);
if (resId == 0) {
return null;
}
final String spokenText = context.getString(resId);
if (!TextUtils.isEmpty(spokenText)) {
return spokenText;
}
// If a translated description is empty, fall back to unknown symbol description.
return context.getString(R.string.spoken_symbol_unknown);
}
// TODO: Remove this method once TTS supports emoji verbalization.
private String getSpokenEmojiDescription(final Context context, final int code) {
final int resId = getSpokenDescriptionId(context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT);
if (resId == 0) {
return null;
}
final String spokenText = context.getString(resId);
if (!TextUtils.isEmpty(spokenText)) {
return spokenText;
}
// If a translated description is empty, fall back to unknown emoji description.
return context.getString(R.string.spoken_emoji_unknown);
}
private int getSpokenDescriptionId(final Context context, final int code,
final String resourceNameFormat) {
final String resourceName = String.format(Locale.ROOT, resourceNameFormat, code);
final Resources resources = context.getResources();
// Note that the resource package name may differ from the context package name.
final String resourcePackageName = resources.getResourcePackageName(
R.string.spoken_description_unknown);
final int resId = resources.getIdentifier(resourceName, "string", resourcePackageName);
if (resId != 0) {
mKeyCodeMap.append(code, resId);
}
return resId;
}
}