Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aaefd566 authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka
Browse files

Add !string/<resource_name> reference

This CL introduces new text reference notation !string/<resource_name>
to refer a string resource on the fly.

This notation is mainly used to represent action key labels may refer
a string in a system locale in run-time.

This notation is needed to implement Hinglish and Serbian-Latin
keyboards that need to refer its own action key labels.

Bug: 17169632
Bug: 9687668
Change-Id: I042f6bd04714e0e448cd92031730eb9fb422e6d3
parent dbb2182e
Loading
Loading
Loading
Loading
+47 −45
Original line number Diff line number Diff line
@@ -25,49 +25,42 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.RunInLocale;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;

import java.util.HashMap;
import java.util.Locale;

// TODO: Make this an immutable class.
public final class KeyboardTextsSet {
    public static final String PREFIX_TEXT = "!text/";
    private static final String PREFIX_RESOURCE = "!string/";
    public static final String SWITCH_TO_ALPHA_KEY_LABEL = "keylabel_to_alpha";

    private static final char BACKSLASH = Constants.CODE_BACKSLASH;
    private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
    private static final int MAX_REFERENCE_INDIRECTION = 10;

    private Resources mResources;
    private Locale mResourceLocale;
    private String mResourcePackageName;
    private String[] mTextsTable;
    // Resource name to text map.
    private HashMap<String, String> mResourceNameToTextsMap = new HashMap<>();

    public void setLocale(final Locale locale, final Context context) {
        mTextsTable = KeyboardTextsTable.getTextsTable(locale);
        final Resources res = context.getResources();
        final int referenceId = context.getApplicationInfo().labelRes;
        final String resourcePackageName = res.getResourcePackageName(referenceId);
        final RunInLocale<Void> job = new RunInLocale<Void>() {
            @Override
            protected Void job(final Resources resource) {
                loadStringResourcesInternal(res, RESOURCE_NAMES, resourcePackageName);
                return null;
            }
        };
        // Null means the current system locale.
        job.runInLocale(res,
                SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale);
        final String resourcePackageName = res.getResourcePackageName(
                context.getApplicationInfo().labelRes);
        setLocale(locale, res, resourcePackageName);
    }

    @UsedForTesting
    void loadStringResourcesInternal(final Resources res, final String[] resourceNames,
    public void setLocale(final Locale locale, final Resources res,
            final String resourcePackageName) {
        for (final String resName : resourceNames) {
            final int resId = res.getIdentifier(resName, "string", resourcePackageName);
            mResourceNameToTextsMap.put(resName, res.getString(resId));
        }
        mResources = res;
        // Null means the current system locale.
        mResourceLocale = SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale;
        mResourcePackageName = resourcePackageName;
        mTextsTable = KeyboardTextsTable.getTextsTable(locale);
    }

    public String getText(final String name) {
        final String text = mResourceNameToTextsMap.get(name);
        return (text != null) ? text : KeyboardTextsTable.getText(name, mTextsTable);
        return KeyboardTextsTable.getText(name, mTextsTable);
    }

    private static int searchTextNameEnd(final String text, final int start) {
@@ -93,13 +86,14 @@ public final class KeyboardTextsSet {
        StringBuilder sb;
        do {
            level++;
            if (level >= MAX_STRING_REFERENCE_INDIRECTION) {
                throw new RuntimeException("Too many " + PREFIX_TEXT + "name indirection: " + text);
            if (level >= MAX_REFERENCE_INDIRECTION) {
                throw new RuntimeException("Too many " + PREFIX_TEXT + " or " + PREFIX_RESOURCE +
                        " reference indirection: " + text);
            }

            final int prefixLen = PREFIX_TEXT.length();
            final int prefixLength = PREFIX_TEXT.length();
            final int size = text.length();
            if (size < prefixLen) {
            if (size < prefixLength) {
                break;
            }

@@ -110,10 +104,12 @@ public final class KeyboardTextsSet {
                    if (sb == null) {
                        sb = new StringBuilder(text.substring(0, pos));
                    }
                    final int end = searchTextNameEnd(text, pos + prefixLen);
                    final String name = text.substring(pos + prefixLen, end);
                    sb.append(getText(name));
                    pos = end - 1;
                    pos = expandReference(text, pos, PREFIX_TEXT, sb);
                } else if (text.startsWith(PREFIX_RESOURCE, pos)) {
                    if (sb == null) {
                        sb = new StringBuilder(text.substring(0, pos));
                    }
                    pos = expandReference(text, pos, PREFIX_RESOURCE, sb);
                } else if (c == BACKSLASH) {
                    if (sb != null) {
                        // Append both escape character and escaped character.
@@ -132,18 +128,24 @@ public final class KeyboardTextsSet {
        return TextUtils.isEmpty(text) ? null : text;
    }

    // These texts' name should be aligned with the @string/<name> in
    // values*/strings-action-keys.xml.
    static final String[] RESOURCE_NAMES = {
        // Labels for action.
        "label_go_key",
        "label_send_key",
        "label_next_key",
        "label_done_key",
        "label_search_key",
        "label_previous_key",
        // Other labels.
        "label_pause_key",
        "label_wait_key",
    private int expandReference(final String text, final int pos, final String prefix,
            final StringBuilder sb) {
        final int prefixLength = prefix.length();
        final int end = searchTextNameEnd(text, pos + prefixLength);
        final String name = text.substring(pos + prefixLength, end);
        if (prefix.equals(PREFIX_TEXT)) {
            sb.append(getText(name));
        } else { // PREFIX_RESOURCE
            final String resourcePackageName = mResourcePackageName;
            final RunInLocale<String> getTextJob = new RunInLocale<String>() {
                @Override
                protected String job(final Resources res) {
                    final int resId = res.getIdentifier(name, "string", resourcePackageName);
                    return res.getString(resId);
                }
            };
            sb.append(getTextJob.runInLocale(mResources, mResourceLocale));
        }
        return end - 1;
    }
}
+78 −44
Original line number Diff line number Diff line
@@ -209,48 +209,56 @@ public final class KeyboardTextsTable {
        /* 123: 1 */ "morekeys_less_than",
        /* 124: 1 */ "morekeys_greater_than",
        /* 125: 1 */ "morekeys_exclamation",
        /* 126: 0 */ "morekeys_currency_generic",
        /* 127: 0 */ "morekeys_symbols_1",
        /* 128: 0 */ "morekeys_symbols_2",
        /* 129: 0 */ "morekeys_symbols_3",
        /* 130: 0 */ "morekeys_symbols_4",
        /* 131: 0 */ "morekeys_symbols_5",
        /* 132: 0 */ "morekeys_symbols_6",
        /* 133: 0 */ "morekeys_symbols_7",
        /* 134: 0 */ "morekeys_symbols_8",
        /* 135: 0 */ "morekeys_symbols_9",
        /* 136: 0 */ "morekeys_symbols_0",
        /* 137: 0 */ "morekeys_am_pm",
        /* 138: 0 */ "keyspec_settings",
        /* 139: 0 */ "keyspec_shortcut",
        /* 140: 0 */ "keyspec_action_next",
        /* 141: 0 */ "keyspec_action_previous",
        /* 142: 0 */ "keylabel_to_more_symbol",
        /* 143: 0 */ "keylabel_tablet_to_more_symbol",
        /* 144: 0 */ "keylabel_to_phone_numeric",
        /* 145: 0 */ "keylabel_to_phone_symbols",
        /* 146: 0 */ "keylabel_time_am",
        /* 147: 0 */ "keylabel_time_pm",
        /* 148: 0 */ "keyspec_popular_domain",
        /* 149: 0 */ "morekeys_popular_domain",
        /* 150: 0 */ "keyspecs_left_parenthesis_more_keys",
        /* 151: 0 */ "keyspecs_right_parenthesis_more_keys",
        /* 152: 0 */ "single_laqm_raqm",
        /* 153: 0 */ "single_raqm_laqm",
        /* 154: 0 */ "double_laqm_raqm",
        /* 155: 0 */ "double_raqm_laqm",
        /* 156: 0 */ "single_lqm_rqm",
        /* 157: 0 */ "single_9qm_lqm",
        /* 158: 0 */ "single_9qm_rqm",
        /* 159: 0 */ "single_rqm_9qm",
        /* 160: 0 */ "double_lqm_rqm",
        /* 161: 0 */ "double_9qm_lqm",
        /* 162: 0 */ "double_9qm_rqm",
        /* 163: 0 */ "double_rqm_9qm",
        /* 164: 0 */ "morekeys_single_quote",
        /* 165: 0 */ "morekeys_double_quote",
        /* 166: 0 */ "morekeys_tablet_double_quote",
        /* 167: 0 */ "keyspec_emoji_action_key",
        /* 126: 1 */ "label_go_key",
        /* 127: 1 */ "label_send_key",
        /* 128: 1 */ "label_next_key",
        /* 129: 1 */ "label_done_key",
        /* 130: 1 */ "label_search_key",
        /* 131: 1 */ "label_previous_key",
        /* 132: 1 */ "label_pause_key",
        /* 133: 1 */ "label_wait_key",
        /* 134: 0 */ "morekeys_currency_generic",
        /* 135: 0 */ "morekeys_symbols_1",
        /* 136: 0 */ "morekeys_symbols_2",
        /* 137: 0 */ "morekeys_symbols_3",
        /* 138: 0 */ "morekeys_symbols_4",
        /* 139: 0 */ "morekeys_symbols_5",
        /* 140: 0 */ "morekeys_symbols_6",
        /* 141: 0 */ "morekeys_symbols_7",
        /* 142: 0 */ "morekeys_symbols_8",
        /* 143: 0 */ "morekeys_symbols_9",
        /* 144: 0 */ "morekeys_symbols_0",
        /* 145: 0 */ "morekeys_am_pm",
        /* 146: 0 */ "keyspec_settings",
        /* 147: 0 */ "keyspec_shortcut",
        /* 148: 0 */ "keyspec_action_next",
        /* 149: 0 */ "keyspec_action_previous",
        /* 150: 0 */ "keylabel_to_more_symbol",
        /* 151: 0 */ "keylabel_tablet_to_more_symbol",
        /* 152: 0 */ "keylabel_to_phone_numeric",
        /* 153: 0 */ "keylabel_to_phone_symbols",
        /* 154: 0 */ "keylabel_time_am",
        /* 155: 0 */ "keylabel_time_pm",
        /* 156: 0 */ "keyspec_popular_domain",
        /* 157: 0 */ "morekeys_popular_domain",
        /* 158: 0 */ "keyspecs_left_parenthesis_more_keys",
        /* 159: 0 */ "keyspecs_right_parenthesis_more_keys",
        /* 160: 0 */ "single_laqm_raqm",
        /* 161: 0 */ "single_raqm_laqm",
        /* 162: 0 */ "double_laqm_raqm",
        /* 163: 0 */ "double_raqm_laqm",
        /* 164: 0 */ "single_lqm_rqm",
        /* 165: 0 */ "single_9qm_lqm",
        /* 166: 0 */ "single_9qm_rqm",
        /* 167: 0 */ "single_rqm_9qm",
        /* 168: 0 */ "double_lqm_rqm",
        /* 169: 0 */ "double_9qm_lqm",
        /* 170: 0 */ "double_9qm_rqm",
        /* 171: 0 */ "double_rqm_9qm",
        /* 172: 0 */ "morekeys_single_quote",
        /* 173: 0 */ "morekeys_double_quote",
        /* 174: 0 */ "morekeys_tablet_double_quote",
        /* 175: 0 */ "keyspec_emoji_action_key",
    };

    private static final String EMPTY = "";
@@ -379,6 +387,14 @@ public final class KeyboardTextsTable {
        /* morekeys_greater_than */ "!fixedColumnOrder!3,!text/keyspec_right_single_angle_quote,!text/keyspec_greater_than_equal,!text/keyspec_right_double_angle_quote",
        // U+00A1: "¡" INVERTED EXCLAMATION MARK
        /* morekeys_exclamation */ "\u00A1",
        /* label_go_key */ "!string/label_go_key",
        /* label_send_key */ "!string/label_send_key",
        /* label_next_key */ "!string/label_next_key",
        /* label_done_key */ "!string/label_done_key",
        /* label_search_key */ "!string/label_search_key",
        /* label_previous_key */ "!string/label_previous_key",
        /* label_pause_key */ "!string/label_pause_key",
        /* label_wait_key */ "!string/label_wait_key",
        /* morekeys_currency_generic */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
        // U+00B9: "¹" SUPERSCRIPT ONE
        // U+00BD: "½" VULGAR FRACTION ONE HALF
@@ -1885,6 +1901,24 @@ public final class KeyboardTextsTable {
        /* ~ morekeys_s */
        // U+20B9: "₹" INDIAN RUPEE SIGN
        /* keyspec_currency */ "\u20B9",
        /* morekeys_y ~ */
        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
        null, null, null, null, null, null, null, null, null,
        /* ~ morekeys_exclamation */
        /* label_go_key */ "Go",
        /* label_send_key */ "Send",
        /* label_next_key */ "Next",
        /* label_done_key */ "Done",
        /* label_search_key */ "Search",
        /* label_previous_key */ "Prev",
        /* label_pause_key */ "Pause",
        /* label_wait_key */ "Wait",
    };

    /* Locale hr: Croatian */
@@ -3952,7 +3986,7 @@ public final class KeyboardTextsTable {

    private static final Object[] LOCALES_AND_TEXTS = {
    // "locale", TEXT_ARRAY,  /* numberOfNonNullText/lengthOf_TEXT_ARRAY localeName */
        "DEFAULT", TEXTS_DEFAULT, /* 168/168 DEFAULT */
        "DEFAULT", TEXTS_DEFAULT, /* 176/176 DEFAULT */
        "af"     , TEXTS_af,    /*   7/ 13 Afrikaans */
        "ar"     , TEXTS_ar,    /*  55/110 Arabic */
        "az_AZ"  , TEXTS_az_AZ, /*  11/ 18 Azerbaijani (Azerbaijan) */
@@ -3974,7 +4008,7 @@ public final class KeyboardTextsTable {
        "fr"     , TEXTS_fr,    /*  13/ 62 French */
        "gl_ES"  , TEXTS_gl_ES, /*   7/  8 Gallegan (Spain) */
        "hi"     , TEXTS_hi,    /*  23/ 53 Hindi */
        "hi_ZZ"  , TEXTS_hi_ZZ, /*   1/ 12 Hindi (ZZ) */
        "hi_ZZ"  , TEXTS_hi_ZZ, /*   9/134 Hindi (ZZ) */
        "hr"     , TEXTS_hr,    /*   9/ 20 Croatian */
        "hu"     , TEXTS_hu,    /*   9/ 20 Hungarian */
        "hy_AM"  , TEXTS_hy_AM, /*   9/126 Armenian (Armenia) */
+1 −1
Original line number Diff line number Diff line
@@ -167,7 +167,7 @@ public final class SubtypeLocaleUtils {
        return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId;
    }

    private static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
    public static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
        if (NO_LANGUAGE.equals(localeString)) {
            return sResources.getConfiguration().locale;
        }
+10 −8
Original line number Diff line number Diff line
@@ -50,13 +50,15 @@
    <string name="multiple_labels_with_escape_surrounded_by_spaces">" \\abc , d\\ef , gh\\i "</string>
    <string name="multiple_labels_with_comma_and_escape">"ab\\\\,d\\\\\\,,g\\,i"</string>
    <string name="multiple_labels_with_comma_and_escape_surrounded_by_spaces">" ab\\\\ , d\\\\\\, , g\\,i "</string>
    <string name="indirect_string">!text/multiple_chars</string>
    <string name="indirect_string_with_literal">x,!text/multiple_chars,y</string>
    <string name="indirect2_string">!text/indirect_string</string>
    <string name="infinite_indirection">infinite,!text/infinite_indirection,loop</string>
    <string name="upper_indirect_string">!TEXT/MULTIPLE_CHARS</string>
    <string name="upper_indirect_string_with_literal">x,!TEXT/MULTIPLE_CHARS,y</string>
    <string name="upper_indirect2_string">!TEXT/UPPER_INDIRECT_STRING</string>
    <string name="upper_infinite_indirection">infinite,!TEXT/INFINITE_INDIRECTION,loop</string>
    <string name="indirect_string">!string/multiple_chars</string>
    <string name="indirect_string_with_literal">x,!string/multiple_chars,y</string>
    <string name="indirect2_string">!string/indirect_string</string>
    <string name="infinite_indirection">infinite,!string/infinite_indirection,loop</string>
    <string name="upper_indirect_string">!STRING/MULTIPLE_CHARS</string>
    <string name="upper_indirect_string_with_literal">x,!STRING/MULTIPLE_CHARS,y</string>
    <string name="upper_indirect2_string">!STRING/UPPER_INDIRECT_STRING</string>
    <string name="upper_infinite_indirection">infinite,!STRING/INFINITE_INDIRECTION,loop</string>
    <string name="keyspec_indirect_navigate_actions">!fixedColumnOrder!2,!text/keyspec_action_previous,!text/keyspec_action_next</string>
    <string name="label_next_key">ActionNext</string>
    <string name="label_previous_key">ActionPrevious</string>
</resources>
+4 −2
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyVisual;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.RunInLocale;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;

@@ -64,10 +65,11 @@ abstract class KeyboardLayoutSetActionLabelBase extends KeyboardLayoutSetTestsBa
    }

    protected static Locale getLabelLocale(final InputMethodSubtype subtype) {
        if (subtype.getLocale().equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
        final String localeString = subtype.getLocale();
        if (localeString.equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
            return null;
        }
        return SubtypeLocaleUtils.getSubtypeLocale(subtype);
        return LocaleUtils.constructLocaleFromString(localeString);
    }

    public void testActionUnspecified() {
Loading