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.common.Constants;
25 import com.android.inputmethod.latin.utils.RunInLocale;
26 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
27 
28 import java.util.Locale;
29 
30 // TODO: Make this an immutable class.
31 public final class KeyboardTextsSet {
32     public static final String PREFIX_TEXT = "!text/";
33     private static final String PREFIX_RESOURCE = "!string/";
34     public static final String SWITCH_TO_ALPHA_KEY_LABEL = "keylabel_to_alpha";
35 
36     private static final char BACKSLASH = Constants.CODE_BACKSLASH;
37     private static final int MAX_REFERENCE_INDIRECTION = 10;
38 
39     private Resources mResources;
40     private Locale mResourceLocale;
41     private String mResourcePackageName;
42     private String[] mTextsTable;
43 
setLocale(final Locale locale, final Context context)44     public void setLocale(final Locale locale, final Context context) {
45         final Resources res = context.getResources();
46         // Null means the current system locale.
47         final String resourcePackageName = res.getResourcePackageName(
48                 context.getApplicationInfo().labelRes);
49         setLocale(locale, res, resourcePackageName);
50     }
51 
52     @UsedForTesting
setLocale(final Locale locale, final Resources res, final String resourcePackageName)53     public void setLocale(final Locale locale, final Resources res,
54             final String resourcePackageName) {
55         mResources = res;
56         // Null means the current system locale.
57         mResourceLocale = SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale;
58         mResourcePackageName = resourcePackageName;
59         mTextsTable = KeyboardTextsTable.getTextsTable(locale);
60     }
61 
getText(final String name)62     public String getText(final String name) {
63         return KeyboardTextsTable.getText(name, mTextsTable);
64     }
65 
searchTextNameEnd(final String text, final int start)66     private static int searchTextNameEnd(final String text, final int start) {
67         final int size = text.length();
68         for (int pos = start; pos < size; pos++) {
69             final char c = text.charAt(pos);
70             // Label name should be consisted of [a-zA-Z_0-9].
71             if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) {
72                 continue;
73             }
74             return pos;
75         }
76         return size;
77     }
78 
79     // TODO: Resolve text reference when creating {@link KeyboardTextsTable} class.
resolveTextReference(final String rawText)80     public String resolveTextReference(final String rawText) {
81         if (TextUtils.isEmpty(rawText)) {
82             return null;
83         }
84         int level = 0;
85         String text = rawText;
86         StringBuilder sb;
87         do {
88             level++;
89             if (level >= MAX_REFERENCE_INDIRECTION) {
90                 throw new RuntimeException("Too many " + PREFIX_TEXT + " or " + PREFIX_RESOURCE +
91                         " reference indirection: " + text);
92             }
93 
94             final int prefixLength = PREFIX_TEXT.length();
95             final int size = text.length();
96             if (size < prefixLength) {
97                 break;
98             }
99 
100             sb = null;
101             for (int pos = 0; pos < size; pos++) {
102                 final char c = text.charAt(pos);
103                 if (text.startsWith(PREFIX_TEXT, pos)) {
104                     if (sb == null) {
105                         sb = new StringBuilder(text.substring(0, pos));
106                     }
107                     pos = expandReference(text, pos, PREFIX_TEXT, sb);
108                 } else if (text.startsWith(PREFIX_RESOURCE, pos)) {
109                     if (sb == null) {
110                         sb = new StringBuilder(text.substring(0, pos));
111                     }
112                     pos = expandReference(text, pos, PREFIX_RESOURCE, sb);
113                 } else if (c == BACKSLASH) {
114                     if (sb != null) {
115                         // Append both escape character and escaped character.
116                         sb.append(text.substring(pos, Math.min(pos + 2, size)));
117                     }
118                     pos++;
119                 } else if (sb != null) {
120                     sb.append(c);
121                 }
122             }
123 
124             if (sb != null) {
125                 text = sb.toString();
126             }
127         } while (sb != null);
128         return TextUtils.isEmpty(text) ? null : text;
129     }
130 
expandReference(final String text, final int pos, final String prefix, final StringBuilder sb)131     private int expandReference(final String text, final int pos, final String prefix,
132             final StringBuilder sb) {
133         final int prefixLength = prefix.length();
134         final int end = searchTextNameEnd(text, pos + prefixLength);
135         final String name = text.substring(pos + prefixLength, end);
136         if (prefix.equals(PREFIX_TEXT)) {
137             sb.append(getText(name));
138         } else { // PREFIX_RESOURCE
139             final String resourcePackageName = mResourcePackageName;
140             final RunInLocale<String> getTextJob = new RunInLocale<String>() {
141                 @Override
142                 protected String job(final Resources res) {
143                     final int resId = res.getIdentifier(name, "string", resourcePackageName);
144                     return res.getString(resId);
145                 }
146             };
147             sb.append(getTextJob.runInLocale(mResources, mResourceLocale));
148         }
149         return end - 1;
150     }
151 }
152