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