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 
initSystem(@onNull BridgeContext context, @NonNull AssetManager assets, @NonNull DisplayMetrics metrics, @NonNull Configuration config, @NonNull LayoutlibCallback layoutlibCallback)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
getContext(@onNull Resources resources)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
getLayoutlibCallback(@onNull Resources resources)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      */
disposeSystem()120     public static void disposeSystem() {
121         sDrawableCache.evictAll();
122         sContexts.clear();
123         sLayoutlibCallbacks.clear();
124         Resources.mSystem = null;
125     }
126 
newTypeArray(Resources resources, int numEntries)127     public static BridgeTypedArray newTypeArray(Resources resources, int numEntries) {
128         return new BridgeTypedArray(resources, getContext(resources), numEntries);
129     }
130 
getResourceInfo(Resources resources, int id)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 
getResourceValue(Resources resources, int id)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
getDrawable(Resources resources, int id)170     static Drawable getDrawable(Resources resources, int id) {
171         return getDrawable(resources, id, null);
172     }
173 
174     @LayoutlibDelegate
getDrawable(Resources resources, int id, Theme theme)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
getColor(Resources resources, int id)204     static int getColor(Resources resources, int id) {
205         return getColor(resources, id, null);
206     }
207 
208     @LayoutlibDelegate
getColor(Resources resources, int id, Theme theme)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
getColorStateList(Resources resources, int id)240     static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException {
241         return getColorStateList(resources, id, null);
242     }
243 
244     @LayoutlibDelegate
getColorStateList(Resources resources, int id, Theme theme)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
getText(Resources resources, int id, CharSequence def)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
getText(Resources resources, int id)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
getTextArray(Resources resources, int id)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
getStringArray(Resources resources, int id)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
resolveValues(@onNull Resources resources, @NonNull ArrayResourceValue resValue)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
getIntArray(Resources resources, int id)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
getArrayResourceValue(Resources resources, int id)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
resolveReference(@onNull Resources resources, @Nullable String value, @NonNull ResourceNamespace contextNamespace, @NonNull ResourceNamespace.Resolver resolver)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
resolveReference(@onNull Resources resources, @NonNull ResourceValue value)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
getLayout(Resources resources, int id)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
getAnimation(Resources resources, int id)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
obtainAttributes(Resources resources, AttributeSet set, int[] attrs)527     static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) {
528         return getContext(resources).obtainStyledAttributes(set, attrs);
529     }
530 
531     @LayoutlibDelegate
obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet set, int[] attrs)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
obtainTypedArray(Resources resources, int id)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
getDimension(Resources resources, int id)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
getDimensionPixelOffset(Resources resources, int id)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
getDimensionPixelSize(Resources resources, int id)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
getInteger(Resources resources, int id)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
getFloat(Resources resources, int id)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
getBoolean(Resources resources, int id)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
getResourceEntryName(Resources resources, int resid)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
getResourceName(Resources resources, int resid)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
getResourcePackageName(Resources resources, int resid)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
getResourceTypeName(Resources resources, int resid)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 
getPackageName(ResourceReference resourceInfo, Resources resources)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
getString(Resources resources, int id, Object... formatArgs)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
getString(Resources resources, int id)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
getQuantityString(Resources resources, int id, int quantity)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
getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)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
getQuantityText(Resources resources, int id, int quantity)853     static CharSequence getQuantityText(Resources resources, int id, int quantity) throws
854             NotFoundException {
855         return getQuantityString(resources, id, quantity);
856     }
857 
858     @LayoutlibDelegate
getFont(Resources resources, int id)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
getFont(Resources resources, TypedValue outValue, int id)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
getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)885     static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
886             throws NotFoundException {
887         getResourceValue(resources, id, outValue);
888     }
889 
getResourceValue(Resources resources, int id, TypedValue outValue)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
getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)921     static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)
922             throws NotFoundException {
923         throw new UnsupportedOperationException();
924     }
925 
926     @LayoutlibDelegate
getValueForDensity(Resources resources, int id, int density, TypedValue outValue, boolean resolveRefs)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
getAttributeSetSourceResId(@ullable AttributeSet set)933     static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
934         // Not supported in layoutlib
935         return Resources.ID_NULL;
936     }
937 
938     @LayoutlibDelegate
getXml(Resources resources, int id)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
loadXmlResourceParser(Resources resources, int id, String type)962     static XmlResourceParser loadXmlResourceParser(Resources resources, int id,
963             String type) throws NotFoundException {
964         return resources.loadXmlResourceParser_Original(id, type);
965     }
966 
967     @LayoutlibDelegate
loadXmlResourceParser(Resources resources, String file, int id, int assetCookie, String type)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
openRawResource(Resources resources, int id)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
openRawResource(Resources resources, int id, TypedValue value)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 
openRawResource(Resources resources, String path)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
openRawResourceFd(Resources resources, int id)1044     static AssetFileDescriptor openRawResourceFd(Resources resources, int id)
1045             throws NotFoundException {
1046         throw new UnsupportedOperationException();
1047     }
1048 
1049     @VisibleForTesting
1050     @Nullable
resourceUrlFromName( @onNull String name, @Nullable String defType, @Nullable String defPackage)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
getIdentifier(Resources resources, String name, String defType, String defPackage)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      */
throwException(Resources resources, int id, @Nullable String expectedType)1125     private static void throwException(Resources resources, int id, @Nullable String expectedType)
1126             throws NotFoundException {
1127         throwException(id, getResourceInfo(resources, id), expectedType);
1128     }
1129 
throwException(Resources resources, int id)1130     private static void throwException(Resources resources, int id) throws NotFoundException {
1131         throwException(resources, id, null);
1132     }
1133 
throwException(int id, @Nullable ResourceReference resourceInfo)1134     private static void throwException(int id, @Nullable ResourceReference resourceInfo) {
1135         throwException(id, resourceInfo, null);
1136     }
throwException(int id, @Nullable ResourceReference resourceInfo, @Nullable String expectedType)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 
getInt(String v)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 
getAssetRepository(Resources resources)1165     private static AssetRepository getAssetRepository(Resources resources) {
1166         BridgeContext context = getContext(resources);
1167         BridgeAssetManager assetManager = context.getAssets();
1168         return assetManager.getAssetRepository();
1169     }
1170 }
1171