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