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; 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.Log; 24 import android.util.TypedValue; 25 26 import com.android.inputmethod.annotations.UsedForTesting; 27 28 import java.util.ArrayList; 29 import java.util.HashMap; 30 31 public final class ResourceUtils { 32 private static final String TAG = ResourceUtils.class.getSimpleName(); 33 34 public static final float UNDEFINED_RATIO = -1.0f; 35 public static final int UNDEFINED_DIMENSION = -1; 36 37 private ResourceUtils() { 38 // This utility class is not publicly instantiable. 39 } 40 41 private static final HashMap<String, String> sDeviceOverrideValueMap = 42 CollectionUtils.newHashMap(); 43 44 private static final String[] BUILD_KEYS_AND_VALUES = { 45 "HARDWARE", Build.HARDWARE, 46 "MODEL", Build.MODEL, 47 "BRAND", Build.BRAND, 48 "MANUFACTURER", Build.MANUFACTURER 49 }; 50 private static final HashMap<String, String> sBuildKeyValues; 51 private static final String sBuildKeyValuesDebugString; 52 53 static { 54 sBuildKeyValues = CollectionUtils.newHashMap(); 55 final ArrayList<String> keyValuePairs = CollectionUtils.newArrayList(); 56 final int keyCount = BUILD_KEYS_AND_VALUES.length / 2; 57 for (int i = 0; i < keyCount; i++) { 58 final int index = i * 2; 59 final String key = BUILD_KEYS_AND_VALUES[index]; 60 final String value = BUILD_KEYS_AND_VALUES[index + 1]; 61 sBuildKeyValues.put(key, value); 62 keyValuePairs.add(key + '=' + value); 63 } 64 sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]"; 65 } 66 67 public static String getDeviceOverrideValue(final Resources res, final int overrideResId) { 68 final int orientation = res.getConfiguration().orientation; 69 final String key = overrideResId + "-" + orientation; 70 if (sDeviceOverrideValueMap.containsKey(key)) { 71 return sDeviceOverrideValueMap.get(key); 72 } 73 74 final String[] overrideArray = res.getStringArray(overrideResId); 75 final String overrideValue = findConstantForKeyValuePairs(sBuildKeyValues, overrideArray); 76 // The overrideValue might be an empty string. 77 if (overrideValue != null) { 78 Log.i(TAG, "Find override value:" 79 + " resource="+ res.getResourceEntryName(overrideResId) 80 + " build=" + sBuildKeyValuesDebugString 81 + " override=" + overrideValue); 82 sDeviceOverrideValueMap.put(key, overrideValue); 83 return overrideValue; 84 } 85 86 final String defaultValue = findDefaultConstant(overrideArray); 87 // The defaultValue might be an empty string. 88 if (defaultValue == null) { 89 Log.w(TAG, "Couldn't find override value nor default value:" 90 + " resource="+ res.getResourceEntryName(overrideResId) 91 + " build=" + sBuildKeyValuesDebugString); 92 } else { 93 Log.i(TAG, "Found default value:" 94 + " resource="+ res.getResourceEntryName(overrideResId) 95 + " build=" + sBuildKeyValuesDebugString 96 + " default=" + defaultValue); 97 } 98 sDeviceOverrideValueMap.put(key, defaultValue); 99 return defaultValue; 100 } 101 102 /** 103 * Find the condition that fulfills specified key value pairs from an array of 104 * "condition,constant", and return the corresponding string constant. A condition is 105 * "pattern1[:pattern2...] (or an empty string for the default). A pattern is 106 * "key=regexp_value" string. The condition matches only if all patterns of the condition 107 * are true for the specified key value pairs. 108 * 109 * For example, "condition,constant" has the following format. 110 * (See {@link ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()}) 111 * - HARDWARE=mako,constantForNexus4 112 * - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4 113 * - ,defaultConstant 114 * 115 * @param keyValuePairs attributes to be used to look for a matched condition. 116 * @param conditionConstantArray an array of "condition,constant" elements to be searched. 117 * @return the constant part of the matched "condition,constant" element. Returns null if no 118 * condition matches. 119 */ 120 @UsedForTesting 121 static String findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs, 122 final String[] conditionConstantArray) { 123 if (conditionConstantArray == null || keyValuePairs == null) { 124 return null; 125 } 126 for (final String conditionConstant : conditionConstantArray) { 127 final int posComma = conditionConstant.indexOf(','); 128 if (posComma < 0) { 129 throw new RuntimeException("Array element has no comma: " + conditionConstant); 130 } 131 final String condition = conditionConstant.substring(0, posComma); 132 if (condition.isEmpty()) { 133 // Default condition. The default condition should be searched by 134 // {@link #findConstantForDefault(String[])}. 135 continue; 136 } 137 if (fulfillsCondition(keyValuePairs, condition)) { 138 return conditionConstant.substring(posComma + 1); 139 } 140 } 141 return null; 142 } 143 144 private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs, 145 final String condition) { 146 final String[] patterns = condition.split(":"); 147 // Check all patterns in a condition are true 148 for (final String pattern : patterns) { 149 final int posEqual = pattern.indexOf('='); 150 if (posEqual < 0) { 151 throw new RuntimeException("Pattern has no '=': " + condition); 152 } 153 final String key = pattern.substring(0, posEqual); 154 final String value = keyValuePairs.get(key); 155 if (value == null) { 156 throw new RuntimeException("Found unknown key: " + condition); 157 } 158 final String patternRegexpValue = pattern.substring(posEqual + 1); 159 if (!value.matches(patternRegexpValue)) { 160 return false; 161 } 162 } 163 return true; 164 } 165 166 @UsedForTesting 167 static String findDefaultConstant(final String[] conditionConstantArray) { 168 if (conditionConstantArray == null) { 169 return null; 170 } 171 for (final String condition : conditionConstantArray) { 172 final int posComma = condition.indexOf(','); 173 if (posComma < 0) { 174 throw new RuntimeException("Array element has no comma: " + condition); 175 } 176 if (posComma == 0) { // condition is empty. 177 return condition.substring(posComma + 1); 178 } 179 } 180 return null; 181 } 182 183 public static boolean isValidFraction(final float fraction) { 184 return fraction >= 0.0f; 185 } 186 187 // {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size. 188 public static boolean isValidDimensionPixelSize(final int dimension) { 189 return dimension > 0; 190 } 191 192 // {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset. 193 public static boolean isValidDimensionPixelOffset(final int dimension) { 194 return dimension >= 0; 195 } 196 197 public static float getFraction(final TypedArray a, final int index, final float defValue) { 198 final TypedValue value = a.peekValue(index); 199 if (value == null || !isFractionValue(value)) { 200 return defValue; 201 } 202 return a.getFraction(index, 1, 1, defValue); 203 } 204 205 public static float getFraction(final TypedArray a, final int index) { 206 return getFraction(a, index, UNDEFINED_RATIO); 207 } 208 209 public static int getDimensionPixelSize(final TypedArray a, final int index) { 210 final TypedValue value = a.peekValue(index); 211 if (value == null || !isDimensionValue(value)) { 212 return ResourceUtils.UNDEFINED_DIMENSION; 213 } 214 return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION); 215 } 216 217 public static float getDimensionOrFraction(final TypedArray a, final int index, final int base, 218 final float defValue) { 219 final TypedValue value = a.peekValue(index); 220 if (value == null) { 221 return defValue; 222 } 223 if (isFractionValue(value)) { 224 return a.getFraction(index, base, base, defValue); 225 } else if (isDimensionValue(value)) { 226 return a.getDimension(index, defValue); 227 } 228 return defValue; 229 } 230 231 public static int getEnumValue(final TypedArray a, final int index, final int defValue) { 232 final TypedValue value = a.peekValue(index); 233 if (value == null) { 234 return defValue; 235 } 236 if (isIntegerValue(value)) { 237 return a.getInt(index, defValue); 238 } 239 return defValue; 240 } 241 242 public static boolean isFractionValue(final TypedValue v) { 243 return v.type == TypedValue.TYPE_FRACTION; 244 } 245 246 public static boolean isDimensionValue(final TypedValue v) { 247 return v.type == TypedValue.TYPE_DIMENSION; 248 } 249 250 public static boolean isIntegerValue(final TypedValue v) { 251 return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT; 252 } 253 254 public static boolean isStringValue(final TypedValue v) { 255 return v.type == TypedValue.TYPE_STRING; 256 } 257 } 258