Home | History | Annotate | Download | only in res
      1 /*
      2  * Copyright (C) 2016 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 android.content.res;
     18 
     19 import com.android.SdkConstants;
     20 import com.android.ide.common.rendering.api.ArrayResourceValue;
     21 import com.android.ide.common.rendering.api.DensityBasedResourceValue;
     22 import com.android.ide.common.rendering.api.LayoutLog;
     23 import com.android.ide.common.rendering.api.LayoutlibCallback;
     24 import com.android.ide.common.rendering.api.PluralsResourceValue;
     25 import com.android.ide.common.rendering.api.RenderResources;
     26 import com.android.ide.common.rendering.api.ResourceValue;
     27 import com.android.layoutlib.bridge.Bridge;
     28 import com.android.layoutlib.bridge.BridgeConstants;
     29 import com.android.layoutlib.bridge.android.BridgeContext;
     30 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
     31 import com.android.layoutlib.bridge.impl.ParserFactory;
     32 import com.android.layoutlib.bridge.impl.ResourceHelper;
     33 import com.android.layoutlib.bridge.util.NinePatchInputStream;
     34 import com.android.ninepatch.NinePatch;
     35 import com.android.resources.ResourceType;
     36 import com.android.resources.ResourceUrl;
     37 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
     38 import com.android.tools.layoutlib.annotations.VisibleForTesting;
     39 import com.android.util.Pair;
     40 
     41 import org.xmlpull.v1.XmlPullParser;
     42 import org.xmlpull.v1.XmlPullParserException;
     43 
     44 import android.annotation.NonNull;
     45 import android.annotation.Nullable;
     46 import android.content.res.Resources.NotFoundException;
     47 import android.content.res.Resources.Theme;
     48 import android.graphics.Color;
     49 import android.graphics.Typeface;
     50 import android.graphics.drawable.Drawable;
     51 import android.icu.text.PluralRules;
     52 import android.util.AttributeSet;
     53 import android.util.DisplayMetrics;
     54 import android.util.LruCache;
     55 import android.util.TypedValue;
     56 import android.view.DisplayAdjustments;
     57 import android.view.ViewGroup.LayoutParams;
     58 
     59 import java.io.File;
     60 import java.io.FileInputStream;
     61 import java.io.FileNotFoundException;
     62 import java.io.InputStream;
     63 import java.util.Iterator;
     64 import java.util.Objects;
     65 import java.util.WeakHashMap;
     66 
     67 import static com.android.SdkConstants.ANDROID_PKG;
     68 import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
     69 
     70 @SuppressWarnings("deprecation")
     71 public class Resources_Delegate {
     72     private static WeakHashMap<Resources, LayoutlibCallback> sLayoutlibCallbacks = new
     73             WeakHashMap<>();
     74     private static WeakHashMap<Resources, BridgeContext> sContexts = new
     75             WeakHashMap<>();
     76 
     77     private static boolean[] mPlatformResourceFlag = new boolean[1];
     78     // TODO: This cache is cleared every time a render session is disposed. Look into making this
     79     // more long lived.
     80     private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50);
     81 
     82     public static Resources initSystem(@NonNull BridgeContext context,
     83             @NonNull AssetManager assets,
     84             @NonNull DisplayMetrics metrics,
     85             @NonNull Configuration config,
     86             @NonNull LayoutlibCallback layoutlibCallback) {
     87         assert Resources.mSystem == null  :
     88                 "Resources_Delegate.initSystem called twice before disposeSystem was called";
     89         Resources resources = new Resources(Resources_Delegate.class.getClassLoader());
     90         resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments()));
     91         sContexts.put(resources, Objects.requireNonNull(context));
     92         sLayoutlibCallbacks.put(resources, Objects.requireNonNull(layoutlibCallback));
     93         return Resources.mSystem = resources;
     94     }
     95 
     96     /** Returns the {@link BridgeContext} associated to the given {@link Resources} */
     97     @VisibleForTesting
     98     @NonNull
     99     public static BridgeContext getContext(@NonNull Resources resources) {
    100         assert sContexts.containsKey(resources) :
    101                 "Resources_Delegate.getContext called before initSystem";
    102         return sContexts.get(resources);
    103     }
    104 
    105     /** Returns the {@link LayoutlibCallback} associated to the given {@link Resources} */
    106     @VisibleForTesting
    107     @NonNull
    108     public static LayoutlibCallback getLayoutlibCallback(@NonNull Resources resources) {
    109         assert sLayoutlibCallbacks.containsKey(resources) :
    110                 "Resources_Delegate.getLayoutlibCallback called before initSystem";
    111         return sLayoutlibCallbacks.get(resources);
    112     }
    113 
    114     /**
    115      * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects around that
    116      * would prevent us from unloading the library.
    117      */
    118     public static void disposeSystem() {
    119         sDrawableCache.evictAll();
    120         sContexts.clear();
    121         sLayoutlibCallbacks.clear();
    122         Resources.mSystem = null;
    123     }
    124 
    125     public static BridgeTypedArray newTypeArray(Resources resources, int numEntries,
    126             boolean platformFile) {
    127         return new BridgeTypedArray(resources, getContext(resources), numEntries, platformFile);
    128     }
    129 
    130     private static Pair<ResourceType, String> getResourceInfo(Resources resources, int id,
    131             boolean[] platformResFlag_out) {
    132         // first get the String related to this id in the framework
    133         Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
    134 
    135         assert Resources.mSystem != null : "Resources_Delegate.initSystem wasn't called";
    136         // Set the layoutlib callback and context for resources
    137         if (resources != Resources.mSystem &&
    138                 (!sContexts.containsKey(resources) || !sLayoutlibCallbacks.containsKey(resources))) {
    139             sLayoutlibCallbacks.put(resources, getLayoutlibCallback(Resources.mSystem));
    140             sContexts.put(resources, getContext(Resources.mSystem));
    141         }
    142 
    143         if (resourceInfo != null) {
    144             platformResFlag_out[0] = true;
    145             return resourceInfo;
    146         }
    147 
    148         // didn't find a match in the framework? look in the project.
    149         resourceInfo = getLayoutlibCallback(resources).resolveResourceId(id);
    150 
    151         if (resourceInfo != null) {
    152             platformResFlag_out[0] = false;
    153             return resourceInfo;
    154         }
    155         return null;
    156     }
    157 
    158     private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
    159             boolean[] platformResFlag_out) {
    160         Pair<ResourceType, String> resourceInfo =
    161                 getResourceInfo(resources, id, platformResFlag_out);
    162 
    163         if (resourceInfo != null) {
    164             String attributeName = resourceInfo.getSecond();
    165             RenderResources renderResources = getContext(resources).getRenderResources();
    166             ResourceValue value = platformResFlag_out[0] ?
    167                     renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) :
    168                     renderResources.getProjectResource(resourceInfo.getFirst(), attributeName);
    169 
    170             if (value == null) {
    171                 // Unable to resolve the attribute, just leave the unresolved value
    172                 value = new ResourceValue(resourceInfo.getFirst(), attributeName, attributeName,
    173                         platformResFlag_out[0]);
    174             }
    175             return Pair.of(attributeName, value);
    176         }
    177 
    178         return null;
    179     }
    180 
    181     @LayoutlibDelegate
    182     static Drawable getDrawable(Resources resources, int id) {
    183         return getDrawable(resources, id, null);
    184     }
    185 
    186     @LayoutlibDelegate
    187     static Drawable getDrawable(Resources resources, int id, Theme theme) {
    188         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    189         if (value != null) {
    190             String key = value.getSecond().getValue();
    191 
    192             Drawable.ConstantState constantState = key != null ? sDrawableCache.get(key) : null;
    193             Drawable drawable;
    194             if (constantState != null) {
    195                 drawable = constantState.newDrawable(resources, theme);
    196             } else {
    197                 drawable =
    198                         ResourceHelper.getDrawable(value.getSecond(), getContext(resources), theme);
    199 
    200                 if (key != null) {
    201                     sDrawableCache.put(key, drawable.getConstantState());
    202                 }
    203             }
    204 
    205             return drawable;
    206         }
    207 
    208         // id was not found or not resolved. Throw a NotFoundException.
    209         throwException(resources, id);
    210 
    211         // this is not used since the method above always throws
    212         return null;
    213     }
    214 
    215     @LayoutlibDelegate
    216     static int getColor(Resources resources, int id) {
    217         return getColor(resources, id, null);
    218     }
    219 
    220     @LayoutlibDelegate
    221     static int getColor(Resources resources, int id, Theme theme) throws NotFoundException {
    222         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    223 
    224         if (value != null) {
    225             ResourceValue resourceValue = value.getSecond();
    226             try {
    227                 return ResourceHelper.getColor(resourceValue.getValue());
    228             } catch (NumberFormatException e) {
    229                 // Check if the value passed is a file. If it is, mostly likely, user is referencing
    230                 // a color state list from a place where they should reference only a pure color.
    231                 String message;
    232                 if (new File(resourceValue.getValue()).isFile()) {
    233                     String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/"
    234                             + resourceValue.getName();
    235                     message = "Hexadecimal color expected, found Color State List for " + resource;
    236                 } else {
    237                     message = e.getMessage();
    238                 }
    239                 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, message, e, null);
    240                 return 0;
    241             }
    242         }
    243 
    244         // Suppress possible NPE. getColorStateList will never return null, it will instead
    245         // throw an exception, but intelliJ can't figure that out
    246         //noinspection ConstantConditions
    247         return getColorStateList(resources, id, theme).getDefaultColor();
    248     }
    249 
    250     @LayoutlibDelegate
    251     static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException {
    252         return getColorStateList(resources, id, null);
    253     }
    254 
    255     @LayoutlibDelegate
    256     static ColorStateList getColorStateList(Resources resources, int id, Theme theme)
    257             throws NotFoundException {
    258         Pair<String, ResourceValue> resValue =
    259                 getResourceValue(resources, id, mPlatformResourceFlag);
    260 
    261         if (resValue != null) {
    262             ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
    263                     getContext(resources), theme);
    264             if (stateList != null) {
    265                 return stateList;
    266             }
    267         }
    268 
    269         // id was not found or not resolved. Throw a NotFoundException.
    270         throwException(resources, id);
    271 
    272         // this is not used since the method above always throws
    273         return null;
    274     }
    275 
    276     @LayoutlibDelegate
    277     static CharSequence getText(Resources resources, int id, CharSequence def) {
    278         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    279 
    280         if (value != null) {
    281             ResourceValue resValue = value.getSecond();
    282 
    283             assert resValue != null;
    284             if (resValue != null) {
    285                 String v = resValue.getValue();
    286                 if (v != null) {
    287                     return v;
    288                 }
    289             }
    290         }
    291 
    292         return def;
    293     }
    294 
    295     @LayoutlibDelegate
    296     static CharSequence getText(Resources resources, int id) throws NotFoundException {
    297         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    298 
    299         if (value != null) {
    300             ResourceValue resValue = value.getSecond();
    301 
    302             assert resValue != null;
    303             if (resValue != null) {
    304                 String v = resValue.getValue();
    305                 if (v != null) {
    306                     return v;
    307                 }
    308             }
    309         }
    310 
    311         // id was not found or not resolved. Throw a NotFoundException.
    312         throwException(resources, id);
    313 
    314         // this is not used since the method above always throws
    315         return null;
    316     }
    317 
    318     @LayoutlibDelegate
    319     static CharSequence[] getTextArray(Resources resources, int id) throws NotFoundException {
    320         ResourceValue resValue = getArrayResourceValue(resources, id);
    321         if (resValue == null) {
    322             // Error already logged by getArrayResourceValue.
    323             return new CharSequence[0];
    324         } else if (!(resValue instanceof ArrayResourceValue)) {
    325             return new CharSequence[]{
    326                     resolveReference(resources, resValue.getValue(), resValue.isFramework())};
    327         }
    328         ArrayResourceValue arv = ((ArrayResourceValue) resValue);
    329         return fillValues(resources, arv, new CharSequence[arv.getElementCount()]);
    330     }
    331 
    332     @LayoutlibDelegate
    333     static String[] getStringArray(Resources resources, int id) throws NotFoundException {
    334         ResourceValue resValue = getArrayResourceValue(resources, id);
    335         if (resValue == null) {
    336             // Error already logged by getArrayResourceValue.
    337             return new String[0];
    338         } else if (!(resValue instanceof ArrayResourceValue)) {
    339             return new String[]{
    340                     resolveReference(resources, resValue.getValue(), resValue.isFramework())};
    341         }
    342         ArrayResourceValue arv = ((ArrayResourceValue) resValue);
    343         return fillValues(resources, arv, new String[arv.getElementCount()]);
    344     }
    345 
    346     /**
    347      * Resolve each element in resValue and copy them to {@code values}. The values copied are
    348      * always Strings. The ideal signature for the method should be &lt;T super String&gt;, but java
    349      * generics don't support it.
    350      */
    351     static <T extends CharSequence> T[] fillValues(Resources resources, ArrayResourceValue resValue,
    352             T[] values) {
    353         int i = 0;
    354         for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
    355             @SuppressWarnings("unchecked")
    356             T s = (T) resolveReference(resources, iterator.next(), resValue.isFramework());
    357             values[i] = s;
    358         }
    359         return values;
    360     }
    361 
    362     @LayoutlibDelegate
    363     static int[] getIntArray(Resources resources, int id) throws NotFoundException {
    364         ResourceValue rv = getArrayResourceValue(resources, id);
    365         if (rv == null) {
    366             // Error already logged by getArrayResourceValue.
    367             return new int[0];
    368         } else if (!(rv instanceof ArrayResourceValue)) {
    369             // This is an older IDE that can only give us the first element of the array.
    370             String firstValue = resolveReference(resources, rv.getValue(), rv.isFramework());
    371             try {
    372                 return new int[]{getInt(firstValue)};
    373             } catch (NumberFormatException e) {
    374                 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
    375                         "Integer resource array contains non-integer value: " +
    376                                 firstValue, null);
    377                 return new int[1];
    378             }
    379         }
    380         ArrayResourceValue resValue = ((ArrayResourceValue) rv);
    381         int[] values = new int[resValue.getElementCount()];
    382         int i = 0;
    383         for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
    384             String element = resolveReference(resources, iterator.next(), resValue.isFramework());
    385             try {
    386                 if (element.startsWith("#")) {
    387                     // This integer represents a color (starts with #)
    388                     values[i] = Color.parseColor(element);
    389                 } else {
    390                     values[i] = getInt(element);
    391                 }
    392             } catch (NumberFormatException e) {
    393                 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
    394                         "Integer resource array contains non-integer value: " + element, null);
    395             } catch (IllegalArgumentException e2) {
    396                 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
    397                         "Integer resource array contains wrong color format: " + element, null);
    398             }
    399         }
    400         return values;
    401     }
    402 
    403     /**
    404      * Try to find the ArrayResourceValue for the given id.
    405      * <p/>
    406      * If the ResourceValue found is not of type {@link ResourceType#ARRAY}, the method logs an
    407      * error and return null. However, if the ResourceValue found has type {@code
    408      * ResourceType.ARRAY}, but the value is not an instance of {@link ArrayResourceValue}, the
    409      * method returns the ResourceValue. This happens on older versions of the IDE, which did not
    410      * parse the array resources properly.
    411      * <p/>
    412      *
    413      * @throws NotFoundException if no resource if found
    414      */
    415     @Nullable
    416     private static ResourceValue getArrayResourceValue(Resources resources, int id)
    417             throws NotFoundException {
    418         Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
    419 
    420         if (v != null) {
    421             ResourceValue resValue = v.getSecond();
    422 
    423             assert resValue != null;
    424             if (resValue != null) {
    425                 final ResourceType type = resValue.getResourceType();
    426                 if (type != ResourceType.ARRAY) {
    427                     Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
    428                             String.format(
    429                                     "Resource with id 0x%1$X is not an array resource, but %2$s",
    430                                     id, type == null ? "null" : type.getDisplayName()),
    431                             null);
    432                     return null;
    433                 }
    434                 if (!(resValue instanceof ArrayResourceValue)) {
    435                     Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED,
    436                             "Obtaining resource arrays via getTextArray, getStringArray or getIntArray is not fully supported in this version of the IDE.",
    437                             null);
    438                 }
    439                 return resValue;
    440             }
    441         }
    442 
    443         // id was not found or not resolved. Throw a NotFoundException.
    444         throwException(resources, id);
    445 
    446         // this is not used since the method above always throws
    447         return null;
    448     }
    449 
    450     @NonNull
    451     private static String resolveReference(Resources resources, @NonNull String ref,
    452             boolean forceFrameworkOnly) {
    453         if (ref.startsWith(PREFIX_RESOURCE_REF) || ref.startsWith
    454                 (SdkConstants.PREFIX_THEME_REF)) {
    455             ResourceValue rv =
    456                     getContext(resources).getRenderResources().findResValue(ref, forceFrameworkOnly);
    457             rv = getContext(resources).getRenderResources().resolveResValue(rv);
    458             if (rv != null) {
    459                 return rv.getValue();
    460             }
    461         }
    462         // Not a reference.
    463         return ref;
    464     }
    465 
    466     @LayoutlibDelegate
    467     static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException {
    468         Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
    469 
    470         if (v != null) {
    471             ResourceValue value = v.getSecond();
    472 
    473             try {
    474                 return ResourceHelper.getXmlBlockParser(getContext(resources), value);
    475             } catch (XmlPullParserException e) {
    476                 Bridge.getLog().error(LayoutLog.TAG_BROKEN,
    477                         "Failed to configure parser for " + value.getValue(), e, null /*data*/);
    478                 // we'll return null below.
    479             } catch (FileNotFoundException e) {
    480                 // this shouldn't happen since we check above.
    481             }
    482 
    483         }
    484 
    485         // id was not found or not resolved. Throw a NotFoundException.
    486         throwException(resources, id);
    487 
    488         // this is not used since the method above always throws
    489         return null;
    490     }
    491 
    492     @LayoutlibDelegate
    493     static XmlResourceParser getAnimation(Resources resources, int id) throws NotFoundException {
    494         Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
    495 
    496         if (v != null) {
    497             ResourceValue value = v.getSecond();
    498 
    499             try {
    500                 return ResourceHelper.getXmlBlockParser(getContext(resources), value);
    501             } catch (XmlPullParserException e) {
    502                 Bridge.getLog().error(LayoutLog.TAG_BROKEN,
    503                         "Failed to configure parser for " + value.getValue(), e, null /*data*/);
    504                 // we'll return null below.
    505             } catch (FileNotFoundException e) {
    506                 // this shouldn't happen since we check above.
    507             }
    508 
    509         }
    510 
    511         // id was not found or not resolved. Throw a NotFoundException.
    512         throwException(resources, id);
    513 
    514         // this is not used since the method above always throws
    515         return null;
    516     }
    517 
    518     @LayoutlibDelegate
    519     static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) {
    520         return getContext(resources).obtainStyledAttributes(set, attrs);
    521     }
    522 
    523     @LayoutlibDelegate
    524     static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet
    525             set, int[] attrs) {
    526         return Resources.obtainAttributes_Original(resources, theme, set, attrs);
    527     }
    528 
    529     @LayoutlibDelegate
    530     static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException {
    531         throw new UnsupportedOperationException();
    532     }
    533 
    534     @LayoutlibDelegate
    535     static float getDimension(Resources resources, int id) throws NotFoundException {
    536         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    537 
    538         if (value != null) {
    539             ResourceValue resValue = value.getSecond();
    540 
    541             assert resValue != null;
    542             if (resValue != null) {
    543                 String v = resValue.getValue();
    544                 if (v != null) {
    545                     if (v.equals(BridgeConstants.MATCH_PARENT) ||
    546                             v.equals(BridgeConstants.FILL_PARENT)) {
    547                         return LayoutParams.MATCH_PARENT;
    548                     } else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
    549                         return LayoutParams.WRAP_CONTENT;
    550                     }
    551                     TypedValue tmpValue = new TypedValue();
    552                     if (ResourceHelper.parseFloatAttribute(
    553                             value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
    554                             tmpValue.type == TypedValue.TYPE_DIMENSION) {
    555                         return tmpValue.getDimension(resources.getDisplayMetrics());
    556                     }
    557                 }
    558             }
    559         }
    560 
    561         // id was not found or not resolved. Throw a NotFoundException.
    562         throwException(resources, id);
    563 
    564         // this is not used since the method above always throws
    565         return 0;
    566     }
    567 
    568     @LayoutlibDelegate
    569     static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException {
    570         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    571 
    572         if (value != null) {
    573             ResourceValue resValue = value.getSecond();
    574 
    575             assert resValue != null;
    576             if (resValue != null) {
    577                 String v = resValue.getValue();
    578                 if (v != null) {
    579                     TypedValue tmpValue = new TypedValue();
    580                     if (ResourceHelper.parseFloatAttribute(
    581                             value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
    582                             tmpValue.type == TypedValue.TYPE_DIMENSION) {
    583                         return TypedValue.complexToDimensionPixelOffset(tmpValue.data,
    584                                 resources.getDisplayMetrics());
    585                     }
    586                 }
    587             }
    588         }
    589 
    590         // id was not found or not resolved. Throw a NotFoundException.
    591         throwException(resources, id);
    592 
    593         // this is not used since the method above always throws
    594         return 0;
    595     }
    596 
    597     @LayoutlibDelegate
    598     static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException {
    599         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    600 
    601         if (value != null) {
    602             ResourceValue resValue = value.getSecond();
    603 
    604             assert resValue != null;
    605             if (resValue != null) {
    606                 String v = resValue.getValue();
    607                 if (v != null) {
    608                     TypedValue tmpValue = new TypedValue();
    609                     if (ResourceHelper.parseFloatAttribute(
    610                             value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
    611                             tmpValue.type == TypedValue.TYPE_DIMENSION) {
    612                         return TypedValue.complexToDimensionPixelSize(tmpValue.data,
    613                                 resources.getDisplayMetrics());
    614                     }
    615                 }
    616             }
    617         }
    618 
    619         // id was not found or not resolved. Throw a NotFoundException.
    620         throwException(resources, id);
    621 
    622         // this is not used since the method above always throws
    623         return 0;
    624     }
    625 
    626     @LayoutlibDelegate
    627     static int getInteger(Resources resources, int id) throws NotFoundException {
    628         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    629 
    630         if (value != null) {
    631             ResourceValue resValue = value.getSecond();
    632 
    633             assert resValue != null;
    634             if (resValue != null) {
    635                 String v = resValue.getValue();
    636                 if (v != null) {
    637                     try {
    638                         return getInt(v);
    639                     } catch (NumberFormatException e) {
    640                         // return exception below
    641                     }
    642                 }
    643             }
    644         }
    645 
    646         // id was not found or not resolved. Throw a NotFoundException.
    647         throwException(resources, id);
    648 
    649         // this is not used since the method above always throws
    650         return 0;
    651     }
    652 
    653     @LayoutlibDelegate
    654     static float getFloat(Resources resources, int id) {
    655         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    656 
    657         if (value != null) {
    658             ResourceValue resValue = value.getSecond();
    659 
    660             if (resValue != null) {
    661                 String v = resValue.getValue();
    662                 if (v != null) {
    663                     try {
    664                         return Float.parseFloat(v);
    665                     } catch (NumberFormatException ignore) {
    666                     }
    667                 }
    668             }
    669         }
    670         return 0;
    671     }
    672 
    673     @LayoutlibDelegate
    674     static boolean getBoolean(Resources resources, int id) throws NotFoundException {
    675         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    676 
    677         if (value != null) {
    678             ResourceValue resValue = value.getSecond();
    679 
    680             if (resValue != null) {
    681                 String v = resValue.getValue();
    682                 if (v != null) {
    683                     return Boolean.parseBoolean(v);
    684                 }
    685             }
    686         }
    687 
    688         // id was not found or not resolved. Throw a NotFoundException.
    689         throwException(resources, id);
    690 
    691         // this is not used since the method above always throws
    692         return false;
    693     }
    694 
    695     @LayoutlibDelegate
    696     static String getResourceEntryName(Resources resources, int resid) throws NotFoundException {
    697         Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
    698         if (resourceInfo != null) {
    699             return resourceInfo.getSecond();
    700         }
    701         throwException(resid, null);
    702         return null;
    703 
    704     }
    705 
    706     @LayoutlibDelegate
    707     static String getResourceName(Resources resources, int resid) throws NotFoundException {
    708         boolean[] platformOut = new boolean[1];
    709         Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
    710         String packageName;
    711         if (resourceInfo != null) {
    712             if (platformOut[0]) {
    713                 packageName = SdkConstants.ANDROID_NS_NAME;
    714             } else {
    715                 packageName = getContext(resources).getPackageName();
    716                 packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName;
    717             }
    718             return packageName + ':' + resourceInfo.getFirst().getName() + '/' +
    719                     resourceInfo.getSecond();
    720         }
    721         throwException(resid, null);
    722         return null;
    723     }
    724 
    725     @LayoutlibDelegate
    726     static String getResourcePackageName(Resources resources, int resid) throws NotFoundException {
    727         boolean[] platformOut = new boolean[1];
    728         Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
    729         if (resourceInfo != null) {
    730             if (platformOut[0]) {
    731                 return SdkConstants.ANDROID_NS_NAME;
    732             }
    733             String packageName = getContext(resources).getPackageName();
    734             return packageName == null ? SdkConstants.APP_PREFIX : packageName;
    735         }
    736         throwException(resid, null);
    737         return null;
    738     }
    739 
    740     @LayoutlibDelegate
    741     static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
    742         Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
    743         if (resourceInfo != null) {
    744             return resourceInfo.getFirst().getName();
    745         }
    746         throwException(resid, null);
    747         return null;
    748     }
    749 
    750     @LayoutlibDelegate
    751     static String getString(Resources resources, int id, Object... formatArgs)
    752             throws NotFoundException {
    753         String s = getString(resources, id);
    754         if (s != null) {
    755             return String.format(s, formatArgs);
    756 
    757         }
    758 
    759         // id was not found or not resolved. Throw a NotFoundException.
    760         throwException(resources, id);
    761 
    762         // this is not used since the method above always throws
    763         return null;
    764     }
    765 
    766     @LayoutlibDelegate
    767     static String getString(Resources resources, int id) throws NotFoundException {
    768         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    769 
    770         if (value != null && value.getSecond().getValue() != null) {
    771             return value.getSecond().getValue();
    772         }
    773 
    774         // id was not found or not resolved. Throw a NotFoundException.
    775         throwException(resources, id);
    776 
    777         // this is not used since the method above always throws
    778         return null;
    779     }
    780 
    781     @LayoutlibDelegate
    782     static String getQuantityString(Resources resources, int id, int quantity) throws
    783             NotFoundException {
    784         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    785 
    786         if (value != null) {
    787             if (value.getSecond() instanceof PluralsResourceValue) {
    788                 PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond();
    789                 PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales()
    790                         .get(0));
    791                 String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity));
    792                 if (strValue == null) {
    793                     strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER);
    794                 }
    795 
    796                 return strValue;
    797             }
    798             else {
    799                 return value.getSecond().getValue();
    800             }
    801         }
    802 
    803         // id was not found or not resolved. Throw a NotFoundException.
    804         throwException(resources, id);
    805 
    806         // this is not used since the method above always throws
    807         return null;
    808     }
    809 
    810     @LayoutlibDelegate
    811     static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)
    812             throws NotFoundException {
    813         String raw = getQuantityString(resources, id, quantity);
    814         return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs);
    815     }
    816 
    817     @LayoutlibDelegate
    818     static CharSequence getQuantityText(Resources resources, int id, int quantity) throws
    819             NotFoundException {
    820         return getQuantityString(resources, id, quantity);
    821     }
    822 
    823     @LayoutlibDelegate
    824     static Typeface getFont(Resources resources, int id) throws
    825             NotFoundException {
    826         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    827         if (value != null) {
    828             return ResourceHelper.getFont(value.getSecond(), getContext(resources), null);
    829         }
    830 
    831         throwException(resources, id);
    832 
    833         // this is not used since the method above always throws
    834         return null;
    835     }
    836 
    837     @LayoutlibDelegate
    838     static Typeface getFont(Resources resources, TypedValue outValue, int id) throws
    839             NotFoundException {
    840         Resources_Delegate.getValue(resources, id, outValue, true);
    841         if (outValue.string != null) {
    842             return ResourceHelper.getFont(outValue.string.toString(), getContext(resources), null,
    843                     mPlatformResourceFlag[0]);
    844         }
    845 
    846         throwException(resources, id);
    847 
    848         // this is not used since the method above always throws
    849         return null;
    850     }
    851 
    852     @LayoutlibDelegate
    853     static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
    854             throws NotFoundException {
    855         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    856 
    857         if (value != null) {
    858             ResourceValue resVal = value.getSecond();
    859             String v = resVal != null ? resVal.getValue() : null;
    860 
    861             if (v != null) {
    862                 if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
    863                         false /*requireUnit*/)) {
    864                     return;
    865                 }
    866                 if (resVal instanceof DensityBasedResourceValue) {
    867                     outValue.density =
    868                             ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
    869                 }
    870 
    871                 // else it's a string
    872                 outValue.type = TypedValue.TYPE_STRING;
    873                 outValue.string = v;
    874                 return;
    875             }
    876         }
    877 
    878         // id was not found or not resolved. Throw a NotFoundException.
    879         throwException(resources, id);
    880     }
    881 
    882     @LayoutlibDelegate
    883     static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)
    884             throws NotFoundException {
    885         throw new UnsupportedOperationException();
    886     }
    887 
    888     @LayoutlibDelegate
    889     static void getValueForDensity(Resources resources, int id, int density, TypedValue outValue,
    890             boolean resolveRefs) throws NotFoundException {
    891         getValue(resources, id, outValue, resolveRefs);
    892     }
    893 
    894     @LayoutlibDelegate
    895     static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
    896         Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
    897 
    898         if (v != null) {
    899             ResourceValue value = v.getSecond();
    900 
    901             try {
    902                 return ResourceHelper.getXmlBlockParser(getContext(resources), value);
    903             } catch (XmlPullParserException e) {
    904                 Bridge.getLog().error(LayoutLog.TAG_BROKEN,
    905                         "Failed to configure parser for " + value.getValue(), e, null /*data*/);
    906                 // we'll return null below.
    907             } catch (FileNotFoundException e) {
    908                 // this shouldn't happen since we check above.
    909             }
    910         }
    911 
    912         // id was not found or not resolved. Throw a NotFoundException.
    913         throwException(resources, id);
    914 
    915         // this is not used since the method above always throws
    916         return null;
    917     }
    918 
    919     @LayoutlibDelegate
    920     static XmlResourceParser loadXmlResourceParser(Resources resources, int id,
    921             String type) throws NotFoundException {
    922         return resources.loadXmlResourceParser_Original(id, type);
    923     }
    924 
    925     @LayoutlibDelegate
    926     static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id,
    927             int assetCookie, String type) throws NotFoundException {
    928         // even though we know the XML file to load directly, we still need to resolve the
    929         // id so that we can know if it's a platform or project resource.
    930         // (mPlatformResouceFlag will get the result and will be used later).
    931         getResourceValue(resources, id, mPlatformResourceFlag);
    932 
    933         File f = new File(file);
    934         try {
    935             XmlPullParser parser = ParserFactory.create(f);
    936 
    937             return new BridgeXmlBlockParser(parser, getContext(resources), mPlatformResourceFlag[0]);
    938         } catch (XmlPullParserException e) {
    939             NotFoundException newE = new NotFoundException();
    940             newE.initCause(e);
    941             throw newE;
    942         } catch (FileNotFoundException e) {
    943             NotFoundException newE = new NotFoundException();
    944             newE.initCause(e);
    945             throw newE;
    946         }
    947     }
    948 
    949     @LayoutlibDelegate
    950     static InputStream openRawResource(Resources resources, int id) throws NotFoundException {
    951         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
    952 
    953         if (value != null) {
    954             String path = value.getSecond().getValue();
    955 
    956             if (path != null) {
    957                 // check this is a file
    958                 File f = new File(path);
    959                 if (f.isFile()) {
    960                     try {
    961                         // if it's a nine-patch return a custom input stream so that
    962                         // other methods (mainly bitmap factory) can detect it's a 9-patch
    963                         // and actually load it as a 9-patch instead of a normal bitmap
    964                         if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
    965                             return new NinePatchInputStream(f);
    966                         }
    967                         return new FileInputStream(f);
    968                     } catch (FileNotFoundException e) {
    969                         NotFoundException newE = new NotFoundException();
    970                         newE.initCause(e);
    971                         throw newE;
    972                     }
    973                 }
    974             }
    975         }
    976 
    977         // id was not found or not resolved. Throw a NotFoundException.
    978         throwException(resources, id);
    979 
    980         // this is not used since the method above always throws
    981         return null;
    982     }
    983 
    984     @LayoutlibDelegate
    985     static InputStream openRawResource(Resources resources, int id, TypedValue value) throws
    986             NotFoundException {
    987         getValue(resources, id, value, true);
    988 
    989         String path = value.string.toString();
    990 
    991         File f = new File(path);
    992         if (f.isFile()) {
    993             try {
    994                 // if it's a nine-patch return a custom input stream so that
    995                 // other methods (mainly bitmap factory) can detect it's a 9-patch
    996                 // and actually load it as a 9-patch instead of a normal bitmap
    997                 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
    998                     return new NinePatchInputStream(f);
    999                 }
   1000                 return new FileInputStream(f);
   1001             } catch (FileNotFoundException e) {
   1002                 NotFoundException exception = new NotFoundException();
   1003                 exception.initCause(e);
   1004                 throw exception;
   1005             }
   1006         }
   1007 
   1008         throw new NotFoundException();
   1009     }
   1010 
   1011     @LayoutlibDelegate
   1012     static AssetFileDescriptor openRawResourceFd(Resources resources, int id) throws
   1013             NotFoundException {
   1014         throw new UnsupportedOperationException();
   1015     }
   1016 
   1017     @VisibleForTesting
   1018     @Nullable
   1019     static ResourceUrl resourceUrlFromName(@NonNull String name, @Nullable String defType,
   1020             @Nullable
   1021             String defPackage) {
   1022         int colonIdx = name.indexOf(':');
   1023         int slashIdx = name.indexOf('/');
   1024 
   1025         if (colonIdx != -1 && slashIdx != -1) {
   1026             // Easy case
   1027             return ResourceUrl.parse(PREFIX_RESOURCE_REF + name);
   1028         }
   1029 
   1030         if (colonIdx == -1 && slashIdx == -1) {
   1031             if (defType == null) {
   1032                 throw new IllegalArgumentException("name does not define a type an no defType was" +
   1033                         " passed");
   1034             }
   1035 
   1036             // It does not define package or type
   1037             return ResourceUrl.parse(
   1038                     PREFIX_RESOURCE_REF + (defPackage != null ? defPackage + ":" : "") + defType +
   1039                             "/" + name);
   1040         }
   1041 
   1042         if (colonIdx != -1) {
   1043             if (defType == null) {
   1044                 throw new IllegalArgumentException("name does not define a type an no defType was" +
   1045                         " passed");
   1046             }
   1047             // We have package but no type
   1048             String pkg = name.substring(0, colonIdx);
   1049             ResourceType type = ResourceType.getEnum(defType);
   1050             return type != null ? ResourceUrl.create(pkg, type, name.substring(colonIdx + 1)) :
   1051                     null;
   1052         }
   1053 
   1054         ResourceType type = ResourceType.getEnum(name.substring(0, slashIdx));
   1055         if (type == null) {
   1056             return null;
   1057         }
   1058         // We have type but no package
   1059         return ResourceUrl.create(defPackage,
   1060                 type,
   1061                 name.substring(slashIdx + 1));
   1062     }
   1063 
   1064     @LayoutlibDelegate
   1065     static int getIdentifier(Resources resources, String name, String defType, String defPackage) {
   1066         if (name == null) {
   1067             return 0;
   1068         }
   1069 
   1070         ResourceUrl url = resourceUrlFromName(name, defType, defPackage);
   1071         Integer id = null;
   1072         if (url != null) {
   1073             id = ANDROID_PKG.equals(url.namespace) ? Bridge.getResourceId(url.type, url.name) :
   1074                     getLayoutlibCallback(resources).getResourceId(url.type, url.name);
   1075         }
   1076 
   1077         return id != null ? id : 0;
   1078     }
   1079 
   1080     /**
   1081      * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource
   1082      * type.
   1083      *
   1084      * @param id the id of the resource
   1085      *
   1086      * @throws NotFoundException
   1087      */
   1088     private static void throwException(Resources resources, int id) throws NotFoundException {
   1089         throwException(id, getResourceInfo(resources, id, new boolean[1]));
   1090     }
   1091 
   1092     private static void throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo) {
   1093         String message;
   1094         if (resourceInfo != null) {
   1095             message = String.format(
   1096                     "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
   1097                     resourceInfo.getFirst(), id, resourceInfo.getSecond());
   1098         } else {
   1099             message = String.format("Could not resolve resource value: 0x%1$X.", id);
   1100         }
   1101 
   1102         throw new NotFoundException(message);
   1103     }
   1104 
   1105     private static int getInt(String v) throws NumberFormatException {
   1106         int radix = 10;
   1107         if (v.startsWith("0x")) {
   1108             v = v.substring(2);
   1109             radix = 16;
   1110         } else if (v.startsWith("0")) {
   1111             radix = 8;
   1112         }
   1113         return Integer.parseInt(v, radix);
   1114     }
   1115 }
   1116