From e9a0e66716dab4dd3184d009d8920de1961efdfa Mon Sep 17 00:00:00 2001 From: Amin Bandali Date: Mon, 16 Dec 2024 21:45:41 -0500 Subject: Rename to Kelar Keyboard (org.kelar.inputmethod.latin) --- .../inputmethod/latin/utils/ResourceUtils.java | 319 +++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 java/src/org/kelar/inputmethod/latin/utils/ResourceUtils.java (limited to 'java/src/org/kelar/inputmethod/latin/utils/ResourceUtils.java') diff --git a/java/src/org/kelar/inputmethod/latin/utils/ResourceUtils.java b/java/src/org/kelar/inputmethod/latin/utils/ResourceUtils.java new file mode 100644 index 000000000..96f206a7b --- /dev/null +++ b/java/src/org/kelar/inputmethod/latin/utils/ResourceUtils.java @@ -0,0 +1,319 @@ +/* + * 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 org.kelar.inputmethod.latin.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Insets; +import android.os.Build; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import org.kelar.inputmethod.annotations.UsedForTesting; +import org.kelar.inputmethod.latin.R; +import org.kelar.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 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 sBuildKeyValues; + private static final String sBuildKeyValuesDebugString; + + static { + sBuildKeyValues = new HashMap<>(); + final ArrayList 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 org.kelar.inputmethod.latin.utils.ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp() + */ + @UsedForTesting + static String findConstantForKeyValuePairs(final HashMap 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 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 Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // Since Android 15’s edge-to-edge enforcement, window insets should be considered. + final WindowManager wm = context.getSystemService(WindowManager.class); + final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); + final Insets insets = + windowMetrics + .getWindowInsets() + .getInsetsIgnoringVisibility( + WindowInsets.Type.systemBars() + | WindowInsets.Type.displayCutout()); + return windowMetrics.getBounds().width() - insets.left - insets.right; + } + final DisplayMetrics dm = context.getResources().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; + } +} -- cgit v1.2.3-83-g751a