1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.keyboard.internal;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.text.TextUtils;
22 
23 import com.android.inputmethod.annotations.UsedForTesting;
24 import com.android.inputmethod.latin.Constants;
25 import com.android.inputmethod.latin.utils.RunInLocale;
26 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
27 
28 import java.util.HashMap;
29 import java.util.Locale;
30 
31 public final class KeyboardTextsSet {
32     public static final String PREFIX_TEXT = "!text/";
33     public static final String SWITCH_TO_ALPHA_KEY_LABEL = "keylabel_to_alpha";
34 
35     private static final char BACKSLASH = Constants.CODE_BACKSLASH;
36     private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
37 
38     private String[] mTextsTable;
39     // Resource name to text map.
40     private HashMap<String, String> mResourceNameToTextsMap = new HashMap<>();
41 
setLocale(final Locale locale, final Context context)42     public void setLocale(final Locale locale, final Context context) {
43         mTextsTable = KeyboardTextsTable.getTextsTable(locale);
44         final Resources res = context.getResources();
45         final int referenceId = context.getApplicationInfo().labelRes;
46         final String resourcePackageName = res.getResourcePackageName(referenceId);
47         final RunInLocale<Void> job = new RunInLocale<Void>() {
48             @Override
49             protected Void job(final Resources resource) {
50                 loadStringResourcesInternal(res, RESOURCE_NAMES, resourcePackageName);
51                 return null;
52             }
53         };
54         // Null means the current system locale.
55         job.runInLocale(res,
56                 SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale);
57     }
58 
59     @UsedForTesting
loadStringResourcesInternal(final Resources res, final String[] resourceNames, final String resourcePackageName)60     void loadStringResourcesInternal(final Resources res, final String[] resourceNames,
61             final String resourcePackageName) {
62         for (final String resName : resourceNames) {
63             final int resId = res.getIdentifier(resName, "string", resourcePackageName);
64             mResourceNameToTextsMap.put(resName, res.getString(resId));
65         }
66     }
67 
getText(final String name)68     public String getText(final String name) {
69         final String text = mResourceNameToTextsMap.get(name);
70         return (text != null) ? text : KeyboardTextsTable.getText(name, mTextsTable);
71     }
72 
searchTextNameEnd(final String text, final int start)73     private static int searchTextNameEnd(final String text, final int start) {
74         final int size = text.length();
75         for (int pos = start; pos < size; pos++) {
76             final char c = text.charAt(pos);
77             // Label name should be consisted of [a-zA-Z_0-9].
78             if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) {
79                 continue;
80             }
81             return pos;
82         }
83         return size;
84     }
85 
86     // TODO: Resolve text reference when creating {@link KeyboardTextsTable} class.
resolveTextReference(final String rawText)87     public String resolveTextReference(final String rawText) {
88         if (TextUtils.isEmpty(rawText)) {
89             return null;
90         }
91         int level = 0;
92         String text = rawText;
93         StringBuilder sb;
94         do {
95             level++;
96             if (level >= MAX_STRING_REFERENCE_INDIRECTION) {
97                 throw new RuntimeException("Too many " + PREFIX_TEXT + "name indirection: " + text);
98             }
99 
100             final int prefixLen = PREFIX_TEXT.length();
101             final int size = text.length();
102             if (size < prefixLen) {
103                 break;
104             }
105 
106             sb = null;
107             for (int pos = 0; pos < size; pos++) {
108                 final char c = text.charAt(pos);
109                 if (text.startsWith(PREFIX_TEXT, pos)) {
110                     if (sb == null) {
111                         sb = new StringBuilder(text.substring(0, pos));
112                     }
113                     final int end = searchTextNameEnd(text, pos + prefixLen);
114                     final String name = text.substring(pos + prefixLen, end);
115                     sb.append(getText(name));
116                     pos = end - 1;
117                 } else if (c == BACKSLASH) {
118                     if (sb != null) {
119                         // Append both escape character and escaped character.
120                         sb.append(text.substring(pos, Math.min(pos + 2, size)));
121                     }
122                     pos++;
123                 } else if (sb != null) {
124                     sb.append(c);
125                 }
126             }
127 
128             if (sb != null) {
129                 text = sb.toString();
130             }
131         } while (sb != null);
132         return TextUtils.isEmpty(text) ? null : text;
133     }
134 
135     // These texts' name should be aligned with the @string/<name> in
136     // values*/strings-action-keys.xml.
137     static final String[] RESOURCE_NAMES = {
138         // Labels for action.
139         "label_go_key",
140         "label_send_key",
141         "label_next_key",
142         "label_done_key",
143         "label_search_key",
144         "label_previous_key",
145         // Other labels.
146         "label_pause_key",
147         "label_wait_key",
148     };
149 }
150