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;
}
}