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