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