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