diff --git a/java/res/values/strings-letter-descriptions.xml b/java/res/values/strings-letter-descriptions.xml new file mode 100644 index 000000000..fbf4671cb --- /dev/null +++ b/java/res/values/strings-letter-descriptions.xml @@ -0,0 +1,322 @@ + + + + + + Feminine ordinal indicator + + Micro sign + + Masculine ordinal indicator + + Sharp S + + A, grave + + A, acute + + A, circumflex + + A, tilde + + A, diaeresis + + A, ring above + + A, E, ligature + + C, cedilla + + E, grave + + E, acute + + E, circumflex + + E, diaeresis + + I, grave + + I, acute + + I, circumflex + + I, diaeresis + + Eth + + N, tilde + + O, grave + + O, acute + + O, circumflex + + O, tilde + + O, diaeresis + + O, stroke + + U, grave + + U, acute + + U, circumflex + + U, diaeresis + + Y, acute + + Thorn + + Y, diaeresis + + A, macron + + A, breve + + A, ogonek + + C, acute + + C, circumflex + + C, dot above + + C, caron + + D, caron + + D, stroke + + E, macron + + E, breve + + E, dot above + + E, ogonek + + E, caron + + G, circumflex + + G, breve + + G, dot above + + G, cedilla + + H, circumflex + + H, stroke + + I, tilde + + I, macron + + I, breve + + I, ogonek + + Dotless I + + I, J, ligature + + J, circumflex + + K, cedilla + + Kra + + L, acute + + L, cedilla + + L, caron + + L, middle dot + + L, stroke + + N, acute + + N, cedilla + + N, caron + + N, preceded by apostrophe + + Eng + + O, macron + + O, breve + + O, double acute + + O, E, ligature + + R, acute + + R, cedilla + + R, caron + + S, acute + + S, circumflex + + S, cedilla + + S, caron + + T, cedilla + + T, caron + + T, stroke + + U, tilde + + U, macron + + U, breve + + U, ring above + + U, double acute + + U, ogonek + + W, circumflex + + Y, circumflex + + Z, acute + + Z, dot above + + Z, caron + + Long S + + O, horn + + U, horn + + S, comma below + + T, comma below + + Schwa + + A, dot below + + A, hook above + + A, circumflex and acute + + A, circumflex and grave + + A, circumflex and hook above + + A, circumflex and tilde + + A, circumflex and dot below + + A, breve and acute + + A, breve and grave + + A, breve and hook above + + A, breve and tilde + + A, breve and dot below + + E, dot below + + E, hook above + + E, tilde + + E, circumflex and acute + + E, circumflex and grave + + E, circumflex and hook above + + E, circumflex and tilde + + E, circumflex and dot below + + I, hook above + + I, dot below + + O, dot below + + O, hook above + + O, circumflex and acute + + O, circumflex and grave + + O, circumflex and hook above + + O, circumflex and tilde + + O, circumflex and dot below + + O, horn and acute + + O, horn and grave + + O, horn and hook above + + O, horn and tilde + + O, horn and dot below + + U, dot below + + U, hook above + + U, horn and acute + + U, horn and grave + + U, horn and hook above + + U, horn and tilde + + U, horn and dot below + + Y, grave + + Y, dot below + + Y, hook above + + Y, tilde + diff --git a/java/res/values/strings-talkback-descriptions.xml b/java/res/values/strings-talkback-descriptions.xml index 80406d02f..14455d088 100644 --- a/java/res/values/strings-talkback-descriptions.xml +++ b/java/res/values/strings-talkback-descriptions.xml @@ -126,4 +126,13 @@ Symbols Emoticons + + + Capital %s + + Capital I + + Capital I, dot above diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java index 46caef625..2c87fc1e9 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -33,6 +33,7 @@ import java.util.Locale; public 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_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X"; // The resource ID of the string spoken for obscured keys @@ -71,6 +72,15 @@ public final class KeyCodeDescriptionMapper { 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); } /** @@ -271,15 +281,19 @@ public final class KeyCodeDescriptionMapper { if (shouldObscure && isDefinedNonCtrl) { return context.getString(OBSCURED_KEY_RES_ID); } - if (mKeyCodeMap.indexOfKey(code) >= 0) { - return context.getString(mKeyCodeMap.get(code)); + final int index = mKeyCodeMap.indexOfKey(code); + if (index >= 0) { + return context.getString(mKeyCodeMap.valueAt(index)); } + final String accentedLetter = getSpokenAccentedLetterDescriptionId(context, code); + if (accentedLetter != null) { + return accentedLetter; + } + // Here, code may be a base letter. final int spokenEmojiId = getSpokenDescriptionId( context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT); if (spokenEmojiId != 0) { - final String spokenEmoji = context.getString(spokenEmojiId); - mKeyCodeMap.append(code, spokenEmojiId); - return spokenEmoji; + return context.getString(spokenEmojiId); } if (isDefinedNonCtrl) { return Character.toString((char) code); @@ -290,12 +304,31 @@ public final class KeyCodeDescriptionMapper { return context.getString(R.string.spoken_description_unknown, code); } - private static int getSpokenDescriptionId(final Context context, final int code, + private String getSpokenAccentedLetterDescriptionId(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.spoke_description_upper_case, spokenText) + : spokenText; + } + + 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(); - final String packageName = resources.getResourcePackageName( + // Note that the resource package name may differ from the context package name. + final String resourcePackageName = resources.getResourcePackageName( R.string.spoken_description_unknown); - return resources.getIdentifier(resourceName, "string", packageName); + final int resId = resources.getIdentifier(resourceName, "string", resourcePackageName); + if (resId != 0) { + mKeyCodeMap.append(code, resId); + } + return resId; } }