2019-12-31 18:19:35 +01:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2012 The Android Open Source Project
|
2023-10-17 13:44:01 +02:00
|
|
|
* modified
|
|
|
|
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
2019-12-31 18:19:35 +01:00
|
|
|
*/
|
|
|
|
|
2024-01-31 18:32:43 +01:00
|
|
|
package helium314.keyboard.latin.utils;
|
2019-12-31 18:19:35 +01:00
|
|
|
|
2023-09-12 14:21:40 +02:00
|
|
|
import android.content.res.Configuration;
|
2019-12-31 18:19:35 +01:00
|
|
|
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.TypedValue;
|
|
|
|
|
2024-01-31 18:32:43 +01:00
|
|
|
import helium314.keyboard.latin.R;
|
|
|
|
import helium314.keyboard.latin.settings.SettingsValues;
|
2019-12-31 18:19:35 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2024-01-01 17:42:26 +01:00
|
|
|
* <p>
|
2019-12-31 18:19:35 +01:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-02-20 16:14:12 +01:00
|
|
|
public static int getKeyboardWidth(final Resources res, final SettingsValues settingsValues) {
|
|
|
|
final int defaultKeyboardWidth = getDefaultKeyboardWidth(res);
|
|
|
|
if (settingsValues.mOneHandedModeEnabled) {
|
2023-12-18 17:30:53 +01:00
|
|
|
return (int) (settingsValues.mOneHandedModeScale * defaultKeyboardWidth);
|
2022-02-20 16:14:12 +01:00
|
|
|
}
|
|
|
|
return defaultKeyboardWidth;
|
|
|
|
}
|
|
|
|
|
2019-12-31 18:19:35 +01:00
|
|
|
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) {
|
2023-12-18 17:30:53 +01:00
|
|
|
final int defaultKeyboardHeight = getDefaultKeyboardHeight(res, settingsValues.mShowsNumberRow);
|
2023-09-06 15:45:41 +02:00
|
|
|
// mKeyboardHeightScale Ranges from [.5,1.5], from xml/prefs_screen_appearance.xml
|
|
|
|
return (int)(defaultKeyboardHeight * settingsValues.mKeyboardHeightScale);
|
2019-12-31 18:19:35 +01:00
|
|
|
}
|
|
|
|
|
2023-12-20 19:46:18 +01:00
|
|
|
public static int getDefaultKeyboardHeight(final Resources res, final boolean showsNumberRow) {
|
2019-12-31 18:19:35 +01:00
|
|
|
final DisplayMetrics dm = res.getDisplayMetrics();
|
2023-12-30 23:38:48 +01:00
|
|
|
final float keyboardHeight = res.getDimension(R.dimen.config_default_keyboard_height) * (showsNumberRow ? 1.33f : 1f);
|
2019-12-31 18:19:35 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 boolean isFractionValue(final TypedValue v) {
|
|
|
|
return v.type == TypedValue.TYPE_FRACTION;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isDimensionValue(final TypedValue v) {
|
|
|
|
return v.type == TypedValue.TYPE_DIMENSION;
|
|
|
|
}
|
|
|
|
|
2023-09-12 14:21:40 +02:00
|
|
|
public static boolean isNight(final Resources res) {
|
|
|
|
return (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
|
|
|
}
|
2019-12-31 18:19:35 +01:00
|
|
|
}
|