• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.latin.utils;
18 
19 import android.content.res.Resources;
20 import android.content.res.TypedArray;
21 import android.os.Build;
22 import android.text.TextUtils;
23 import android.util.DisplayMetrics;
24 import android.util.Log;
25 import android.util.TypedValue;
26 
27 import com.android.inputmethod.annotations.UsedForTesting;
28 import com.android.inputmethod.latin.R;
29 
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.regex.PatternSyntaxException;
33 
34 public final class ResourceUtils {
35     private static final String TAG = ResourceUtils.class.getSimpleName();
36 
37     public static final float UNDEFINED_RATIO = -1.0f;
38     public static final int UNDEFINED_DIMENSION = -1;
39 
ResourceUtils()40     private ResourceUtils() {
41         // This utility class is not publicly instantiable.
42     }
43 
44     private static final HashMap<String, String> sDeviceOverrideValueMap =
45             CollectionUtils.newHashMap();
46 
47     private static final String[] BUILD_KEYS_AND_VALUES = {
48         "HARDWARE", Build.HARDWARE,
49         "MODEL", Build.MODEL,
50         "BRAND", Build.BRAND,
51         "MANUFACTURER", Build.MANUFACTURER
52     };
53     private static final HashMap<String, String> sBuildKeyValues;
54     private static final String sBuildKeyValuesDebugString;
55 
56     static {
57         sBuildKeyValues = CollectionUtils.newHashMap();
58         final ArrayList<String> keyValuePairs = CollectionUtils.newArrayList();
59         final int keyCount = BUILD_KEYS_AND_VALUES.length / 2;
60         for (int i = 0; i < keyCount; i++) {
61             final int index = i * 2;
62             final String key = BUILD_KEYS_AND_VALUES[index];
63             final String value = BUILD_KEYS_AND_VALUES[index + 1];
sBuildKeyValues.put(key, value)64             sBuildKeyValues.put(key, value);
65             keyValuePairs.add(key + '=' + value);
66         }
67         sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]";
68     }
69 
getDeviceOverrideValue(final Resources res, final int overrideResId)70     public static String getDeviceOverrideValue(final Resources res, final int overrideResId) {
71         final int orientation = res.getConfiguration().orientation;
72         final String key = overrideResId + "-" + orientation;
73         if (sDeviceOverrideValueMap.containsKey(key)) {
74             return sDeviceOverrideValueMap.get(key);
75         }
76 
77         final String[] overrideArray = res.getStringArray(overrideResId);
78         final String overrideValue = findConstantForKeyValuePairs(sBuildKeyValues, overrideArray);
79         // The overrideValue might be an empty string.
80         if (overrideValue != null) {
81             Log.i(TAG, "Find override value:"
82                     + " resource="+ res.getResourceEntryName(overrideResId)
83                     + " build=" + sBuildKeyValuesDebugString
84                     + " override=" + overrideValue);
85             sDeviceOverrideValueMap.put(key, overrideValue);
86             return overrideValue;
87         }
88 
89         String defaultValue = null;
90         try {
91             defaultValue = findDefaultConstant(overrideArray);
92             // The defaultValue might be an empty string.
93             if (defaultValue == null) {
94                 Log.w(TAG, "Couldn't find override value nor default value:"
95                         + " resource="+ res.getResourceEntryName(overrideResId)
96                         + " build=" + sBuildKeyValuesDebugString);
97             } else {
98                 Log.i(TAG, "Found default value:"
99                         + " resource="+ res.getResourceEntryName(overrideResId)
100                         + " build=" + sBuildKeyValuesDebugString
101                         + " default=" + defaultValue);
102             }
103         } catch (final DeviceOverridePatternSyntaxError e) {
104             Log.w(TAG, "Syntax error, ignored", e);
105         }
106         sDeviceOverrideValueMap.put(key, defaultValue);
107         return defaultValue;
108     }
109 
110     @SuppressWarnings("serial")
111     static class DeviceOverridePatternSyntaxError extends Exception {
DeviceOverridePatternSyntaxError(final String message, final String expression)112         public DeviceOverridePatternSyntaxError(final String message, final String expression) {
113             this(message, expression, null);
114         }
115 
DeviceOverridePatternSyntaxError(final String message, final String expression, final Throwable throwable)116         public DeviceOverridePatternSyntaxError(final String message, final String expression,
117                 final Throwable throwable) {
118             super(message + ": " + expression, throwable);
119         }
120     }
121 
122     /**
123      * Find the condition that fulfills specified key value pairs from an array of
124      * "condition,constant", and return the corresponding string constant. A condition is
125      * "pattern1[:pattern2...] (or an empty string for the default). A pattern is
126      * "key=regexp_value" string. The condition matches only if all patterns of the condition
127      * are true for the specified key value pairs.
128      *
129      * For example, "condition,constant" has the following format.
130      * (See {@link ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()})
131      *  - HARDWARE=mako,constantForNexus4
132      *  - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4
133      *  - ,defaultConstant
134      *
135      * @param keyValuePairs attributes to be used to look for a matched condition.
136      * @param conditionConstantArray an array of "condition,constant" elements to be searched.
137      * @return the constant part of the matched "condition,constant" element. Returns null if no
138      * condition matches.
139      */
140     @UsedForTesting
findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs, final String[] conditionConstantArray)141     static String findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs,
142             final String[] conditionConstantArray) {
143         if (conditionConstantArray == null || keyValuePairs == null) {
144             return null;
145         }
146         String foundValue = null;
147         for (final String conditionConstant : conditionConstantArray) {
148             final int posComma = conditionConstant.indexOf(',');
149             if (posComma < 0) {
150                 Log.w(TAG, "Array element has no comma: " + conditionConstant);
151                 continue;
152             }
153             final String condition = conditionConstant.substring(0, posComma);
154             if (condition.isEmpty()) {
155                 // Default condition. The default condition should be searched by
156                 // {@link #findConstantForDefault(String[])}.
157                 continue;
158             }
159             try {
160                 if (fulfillsCondition(keyValuePairs, condition)) {
161                     // Take first match
162                     if (foundValue == null) {
163                         foundValue = conditionConstant.substring(posComma + 1);
164                     }
165                     // And continue walking through all conditions.
166                 }
167             } catch (final DeviceOverridePatternSyntaxError e) {
168                 Log.w(TAG, "Syntax error, ignored", e);
169             }
170         }
171         return foundValue;
172     }
173 
fulfillsCondition(final HashMap<String,String> keyValuePairs, final String condition)174     private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs,
175             final String condition) throws DeviceOverridePatternSyntaxError {
176         final String[] patterns = condition.split(":");
177         // Check all patterns in a condition are true
178         boolean matchedAll = true;
179         for (final String pattern : patterns) {
180             final int posEqual = pattern.indexOf('=');
181             if (posEqual < 0) {
182                 throw new DeviceOverridePatternSyntaxError("Pattern has no '='", condition);
183             }
184             final String key = pattern.substring(0, posEqual);
185             final String value = keyValuePairs.get(key);
186             if (value == null) {
187                 throw new DeviceOverridePatternSyntaxError("Unknown key", condition);
188             }
189             final String patternRegexpValue = pattern.substring(posEqual + 1);
190             try {
191                 if (!value.matches(patternRegexpValue)) {
192                     matchedAll = false;
193                     // And continue walking through all patterns.
194                 }
195             } catch (final PatternSyntaxException e) {
196                 throw new DeviceOverridePatternSyntaxError("Syntax error", condition, e);
197             }
198         }
199         return matchedAll;
200     }
201 
202     @UsedForTesting
findDefaultConstant(final String[] conditionConstantArray)203     static String findDefaultConstant(final String[] conditionConstantArray)
204             throws DeviceOverridePatternSyntaxError {
205         if (conditionConstantArray == null) {
206             return null;
207         }
208         for (final String condition : conditionConstantArray) {
209             final int posComma = condition.indexOf(',');
210             if (posComma < 0) {
211                 throw new DeviceOverridePatternSyntaxError("Array element has no comma", condition);
212             }
213             if (posComma == 0) { // condition is empty.
214                 return condition.substring(posComma + 1);
215             }
216         }
217         return null;
218     }
219 
getDefaultKeyboardWidth(final Resources res)220     public static int getDefaultKeyboardWidth(final Resources res) {
221         final DisplayMetrics dm = res.getDisplayMetrics();
222         return dm.widthPixels;
223     }
224 
getDefaultKeyboardHeight(final Resources res)225     public static int getDefaultKeyboardHeight(final Resources res) {
226         final DisplayMetrics dm = res.getDisplayMetrics();
227         final String keyboardHeightString = getDeviceOverrideValue(res, R.array.keyboard_heights);
228         final float keyboardHeight;
229         if (TextUtils.isEmpty(keyboardHeightString)) {
230             keyboardHeight = res.getDimension(R.dimen.keyboardHeight);
231         } else {
232             keyboardHeight = Float.parseFloat(keyboardHeightString) * dm.density;
233         }
234         final float maxKeyboardHeight = res.getFraction(
235                 R.fraction.maxKeyboardHeight, dm.heightPixels, dm.heightPixels);
236         float minKeyboardHeight = res.getFraction(
237                 R.fraction.minKeyboardHeight, dm.heightPixels, dm.heightPixels);
238         if (minKeyboardHeight < 0.0f) {
239             // Specified fraction was negative, so it should be calculated against display
240             // width.
241             minKeyboardHeight = -res.getFraction(
242                     R.fraction.minKeyboardHeight, dm.widthPixels, dm.widthPixels);
243         }
244         // Keyboard height will not exceed maxKeyboardHeight and will not be less than
245         // minKeyboardHeight.
246         return (int)Math.max(Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
247     }
248 
isValidFraction(final float fraction)249     public static boolean isValidFraction(final float fraction) {
250         return fraction >= 0.0f;
251     }
252 
253     // {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size.
isValidDimensionPixelSize(final int dimension)254     public static boolean isValidDimensionPixelSize(final int dimension) {
255         return dimension > 0;
256     }
257 
258     // {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset.
isValidDimensionPixelOffset(final int dimension)259     public static boolean isValidDimensionPixelOffset(final int dimension) {
260         return dimension >= 0;
261     }
262 
getFraction(final TypedArray a, final int index, final float defValue)263     public static float getFraction(final TypedArray a, final int index, final float defValue) {
264         final TypedValue value = a.peekValue(index);
265         if (value == null || !isFractionValue(value)) {
266             return defValue;
267         }
268         return a.getFraction(index, 1, 1, defValue);
269     }
270 
getFraction(final TypedArray a, final int index)271     public static float getFraction(final TypedArray a, final int index) {
272         return getFraction(a, index, UNDEFINED_RATIO);
273     }
274 
getDimensionPixelSize(final TypedArray a, final int index)275     public static int getDimensionPixelSize(final TypedArray a, final int index) {
276         final TypedValue value = a.peekValue(index);
277         if (value == null || !isDimensionValue(value)) {
278             return ResourceUtils.UNDEFINED_DIMENSION;
279         }
280         return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION);
281     }
282 
getDimensionOrFraction(final TypedArray a, final int index, final int base, final float defValue)283     public static float getDimensionOrFraction(final TypedArray a, final int index, final int base,
284             final float defValue) {
285         final TypedValue value = a.peekValue(index);
286         if (value == null) {
287             return defValue;
288         }
289         if (isFractionValue(value)) {
290             return a.getFraction(index, base, base, defValue);
291         } else if (isDimensionValue(value)) {
292             return a.getDimension(index, defValue);
293         }
294         return defValue;
295     }
296 
getEnumValue(final TypedArray a, final int index, final int defValue)297     public static int getEnumValue(final TypedArray a, final int index, final int defValue) {
298         final TypedValue value = a.peekValue(index);
299         if (value == null) {
300             return defValue;
301         }
302         if (isIntegerValue(value)) {
303             return a.getInt(index, defValue);
304         }
305         return defValue;
306     }
307 
isFractionValue(final TypedValue v)308     public static boolean isFractionValue(final TypedValue v) {
309         return v.type == TypedValue.TYPE_FRACTION;
310     }
311 
isDimensionValue(final TypedValue v)312     public static boolean isDimensionValue(final TypedValue v) {
313         return v.type == TypedValue.TYPE_DIMENSION;
314     }
315 
isIntegerValue(final TypedValue v)316     public static boolean isIntegerValue(final TypedValue v) {
317         return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
318     }
319 
isStringValue(final TypedValue v)320     public static boolean isStringValue(final TypedValue v) {
321         return v.type == TypedValue.TYPE_STRING;
322     }
323 }
324