LatinIME/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java

303 lines
12 KiB
Java

/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.latin.utils;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.settings.SettingsValues;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.PatternSyntaxException;
public final class ResourceUtils {
private static final String TAG = ResourceUtils.class.getSimpleName();
public static final float UNDEFINED_RATIO = -1.0f;
public static final int UNDEFINED_DIMENSION = -1;
private ResourceUtils() {
// This utility class is not publicly instantiable.
}
private static final HashMap<String, String> sDeviceOverrideValueMap = new HashMap<>();
private static final String[] BUILD_KEYS_AND_VALUES = {
"HARDWARE", Build.HARDWARE,
"MODEL", Build.MODEL,
"BRAND", Build.BRAND,
"MANUFACTURER", Build.MANUFACTURER
};
private static final HashMap<String, String> sBuildKeyValues;
private static final String sBuildKeyValuesDebugString;
static {
sBuildKeyValues = new HashMap<>();
final ArrayList<String> keyValuePairs = new ArrayList<>();
final int keyCount = BUILD_KEYS_AND_VALUES.length / 2;
for (int i = 0; i < keyCount; i++) {
final int index = i * 2;
final String key = BUILD_KEYS_AND_VALUES[index];
final String value = BUILD_KEYS_AND_VALUES[index + 1];
sBuildKeyValues.put(key, value);
keyValuePairs.add(key + '=' + value);
}
sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]";
}
public static String getDeviceOverrideValue(final Resources res, final int overrideResId,
final String defaultValue) {
final int orientation = res.getConfiguration().orientation;
final String key = overrideResId + "-" + orientation;
if (sDeviceOverrideValueMap.containsKey(key)) {
return sDeviceOverrideValueMap.get(key);
}
final String[] overrideArray = res.getStringArray(overrideResId);
final String overrideValue = findConstantForKeyValuePairs(sBuildKeyValues, overrideArray);
// The overrideValue might be an empty string.
if (overrideValue != null) {
Log.i(TAG, "Find override value:"
+ " resource="+ res.getResourceEntryName(overrideResId)
+ " build=" + sBuildKeyValuesDebugString
+ " override=" + overrideValue);
sDeviceOverrideValueMap.put(key, overrideValue);
return overrideValue;
}
sDeviceOverrideValueMap.put(key, defaultValue);
return defaultValue;
}
@SuppressWarnings("serial")
static class DeviceOverridePatternSyntaxError extends Exception {
public DeviceOverridePatternSyntaxError(final String message, final String expression) {
this(message, expression, null);
}
public DeviceOverridePatternSyntaxError(final String message, final String expression,
final Throwable throwable) {
super(message + ": " + expression, throwable);
}
}
/**
* Find the condition that fulfills specified key value pairs from an array of
* "condition,constant", and return the corresponding string constant. A condition is
* "pattern1[:pattern2...] (or an empty string for the default). A pattern is
* "key=regexp_value" string. The condition matches only if all patterns of the condition
* are true for the specified key value pairs.
*
* For example, "condition,constant" has the following format.
* - HARDWARE=mako,constantForNexus4
* - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4
* - ,defaultConstant
*
* @param keyValuePairs attributes to be used to look for a matched condition.
* @param conditionConstantArray an array of "condition,constant" elements to be searched.
* @return the constant part of the matched "condition,constant" element. Returns null if no
* condition matches.
* @see com.android.inputmethod.latin.utils.ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()
*/
@UsedForTesting
static String findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs,
final String[] conditionConstantArray) {
if (conditionConstantArray == null || keyValuePairs == null) {
return null;
}
String foundValue = null;
for (final String conditionConstant : conditionConstantArray) {
final int posComma = conditionConstant.indexOf(',');
if (posComma < 0) {
Log.w(TAG, "Array element has no comma: " + conditionConstant);
continue;
}
final String condition = conditionConstant.substring(0, posComma);
if (condition.isEmpty()) {
Log.w(TAG, "Array element has no condition: " + conditionConstant);
continue;
}
try {
if (fulfillsCondition(keyValuePairs, condition)) {
// Take first match
if (foundValue == null) {
foundValue = conditionConstant.substring(posComma + 1);
}
// And continue walking through all conditions.
}
} catch (final DeviceOverridePatternSyntaxError e) {
Log.w(TAG, "Syntax error, ignored", e);
}
}
return foundValue;
}
private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs,
final String condition) throws DeviceOverridePatternSyntaxError {
final String[] patterns = condition.split(":");
// Check all patterns in a condition are true
boolean matchedAll = true;
for (final String pattern : patterns) {
final int posEqual = pattern.indexOf('=');
if (posEqual < 0) {
throw new DeviceOverridePatternSyntaxError("Pattern has no '='", condition);
}
final String key = pattern.substring(0, posEqual);
final String value = keyValuePairs.get(key);
if (value == null) {
throw new DeviceOverridePatternSyntaxError("Unknown key", condition);
}
final String patternRegexpValue = pattern.substring(posEqual + 1);
try {
if (!value.matches(patternRegexpValue)) {
matchedAll = false;
// And continue walking through all patterns.
}
} catch (final PatternSyntaxException e) {
throw new DeviceOverridePatternSyntaxError("Syntax error", condition, e);
}
}
return matchedAll;
}
public static int getDefaultKeyboardWidth(final Resources res) {
final DisplayMetrics dm = res.getDisplayMetrics();
return dm.widthPixels;
}
public static int getKeyboardHeight(final Resources res, final SettingsValues settingsValues) {
final int defaultKeyboardHeight = getDefaultKeyboardHeight(res);
if (settingsValues.mHasKeyboardResize) {
// mKeyboardHeightScale Ranges from [.5,1.2], from xml/prefs_screen_debug.xml
return (int)(defaultKeyboardHeight * settingsValues.mKeyboardHeightScale);
}
return defaultKeyboardHeight;
}
public static int getDefaultKeyboardHeight(final Resources res) {
final DisplayMetrics dm = res.getDisplayMetrics();
final String keyboardHeightInDp = getDeviceOverrideValue(
res, R.array.keyboard_heights, null /* defaultValue */);
final float keyboardHeight;
if (TextUtils.isEmpty(keyboardHeightInDp)) {
keyboardHeight = res.getDimension(R.dimen.config_default_keyboard_height);
} else {
keyboardHeight = Float.parseFloat(keyboardHeightInDp) * dm.density;
}
final float maxKeyboardHeight = res.getFraction(
R.fraction.config_max_keyboard_height, dm.heightPixels, dm.heightPixels);
float minKeyboardHeight = res.getFraction(
R.fraction.config_min_keyboard_height, dm.heightPixels, dm.heightPixels);
if (minKeyboardHeight < 0.0f) {
// Specified fraction was negative, so it should be calculated against display
// width.
minKeyboardHeight = -res.getFraction(
R.fraction.config_min_keyboard_height, dm.widthPixels, dm.widthPixels);
}
// Keyboard height will not exceed maxKeyboardHeight and will not be less than
// minKeyboardHeight.
return (int)Math.max(Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
}
public static boolean isValidFraction(final float fraction) {
return fraction >= 0.0f;
}
// {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size.
public static boolean isValidDimensionPixelSize(final int dimension) {
return dimension > 0;
}
// {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset.
public static boolean isValidDimensionPixelOffset(final int dimension) {
return dimension >= 0;
}
public static float getFloatFromFraction(final Resources res, final int fractionResId) {
return res.getFraction(fractionResId, 1, 1);
}
public static float getFraction(final TypedArray a, final int index, final float defValue) {
final TypedValue value = a.peekValue(index);
if (value == null || !isFractionValue(value)) {
return defValue;
}
return a.getFraction(index, 1, 1, defValue);
}
public static float getFraction(final TypedArray a, final int index) {
return getFraction(a, index, UNDEFINED_RATIO);
}
public static int getDimensionPixelSize(final TypedArray a, final int index) {
final TypedValue value = a.peekValue(index);
if (value == null || !isDimensionValue(value)) {
return ResourceUtils.UNDEFINED_DIMENSION;
}
return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION);
}
public static float getDimensionOrFraction(final TypedArray a, final int index, final int base,
final float defValue) {
final TypedValue value = a.peekValue(index);
if (value == null) {
return defValue;
}
if (isFractionValue(value)) {
return a.getFraction(index, base, base, defValue);
} else if (isDimensionValue(value)) {
return a.getDimension(index, defValue);
}
return defValue;
}
public static int getEnumValue(final TypedArray a, final int index, final int defValue) {
final TypedValue value = a.peekValue(index);
if (value == null) {
return defValue;
}
if (isIntegerValue(value)) {
return a.getInt(index, defValue);
}
return defValue;
}
public static boolean isFractionValue(final TypedValue v) {
return v.type == TypedValue.TYPE_FRACTION;
}
public static boolean isDimensionValue(final TypedValue v) {
return v.type == TypedValue.TYPE_DIMENSION;
}
public static boolean isIntegerValue(final TypedValue v) {
return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
}
public static boolean isStringValue(final TypedValue v) {
return v.type == TypedValue.TYPE_STRING;
}
}