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 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 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 62 public String getText(final String name) { 63 return KeyboardTextsTable.getText(name, mTextsTable); 64 } 65 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. 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 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