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.ide.common.rendering.api.ArrayResourceValue;
20 import com.android.ide.common.rendering.api.AssetRepository;
21 import com.android.ide.common.rendering.api.DensityBasedResourceValue;
22 import com.android.ide.common.rendering.api.ILayoutLog;
23 import com.android.ide.common.rendering.api.LayoutlibCallback;
24 import com.android.ide.common.rendering.api.PluralsResourceValue;
25 import com.android.ide.common.rendering.api.RenderResources;
26 import com.android.ide.common.rendering.api.ResourceNamespace;
27 import com.android.ide.common.rendering.api.ResourceReference;
28 import com.android.ide.common.rendering.api.ResourceValue;
29 import com.android.ide.common.rendering.api.ResourceValueImpl;
30 import com.android.layoutlib.bridge.Bridge;
31 import com.android.layoutlib.bridge.BridgeConstants;
32 import com.android.layoutlib.bridge.android.BridgeContext;
33 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
34 import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
35 import com.android.layoutlib.bridge.impl.ParserFactory;
36 import com.android.layoutlib.bridge.impl.ResourceHelper;
37 import com.android.layoutlib.bridge.util.NinePatchInputStream;
38 import com.android.resources.ResourceType;
39 import com.android.resources.ResourceUrl;
40 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
41 import com.android.tools.layoutlib.annotations.VisibleForTesting;
42 
43 import org.xmlpull.v1.XmlPullParser;
44 import org.xmlpull.v1.XmlPullParserException;
45 
46 import android.annotation.NonNull;
47 import android.annotation.Nullable;
48 import android.content.res.Resources.NotFoundException;
49 import android.content.res.Resources.Theme;
50 import android.graphics.Typeface;
51 import android.graphics.drawable.Drawable;
52 import android.graphics.drawable.DrawableInflater_Delegate;
53 import android.icu.text.PluralRules;
54 import android.util.AttributeSet;
55 import android.util.DisplayMetrics;
56 import android.util.LruCache;
57 import android.util.Pair;
58 import android.util.TypedValue;
59 import android.view.DisplayAdjustments;
60 import android.view.ViewGroup.LayoutParams;
61 
62 import java.io.IOException;
63 import java.io.InputStream;
64 import java.util.Objects;
65 import java.util.WeakHashMap;
66 
67 import static android.content.res.AssetManager.ACCESS_STREAMING;
68 import static com.android.ide.common.rendering.api.AndroidConstants.ANDROID_PKG;
69 import static com.android.ide.common.rendering.api.AndroidConstants.APP_PREFIX;
70 import static com.android.ide.common.rendering.api.AndroidConstants.PREFIX_RESOURCE_REF;
71 
72 public class Resources_Delegate {
73     private static WeakHashMap<Resources, LayoutlibCallback> sLayoutlibCallbacks =
74             new WeakHashMap<>();
75     private static WeakHashMap<Resources, BridgeContext> sContexts = new WeakHashMap<>();
76 
77     // TODO: This cache is cleared every time a render session is disposed. Look into making this
78     // more long lived.
79     private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50);
80 
initSystem(@onNull BridgeContext context, @NonNull AssetManager assets, @NonNull DisplayMetrics metrics, @NonNull Configuration config, @NonNull LayoutlibCallback layoutlibCallback)81     public static Resources initSystem(@NonNull BridgeContext context,
82             @NonNull AssetManager assets,
83             @NonNull DisplayMetrics metrics,
84             @NonNull Configuration config,
85             @NonNull LayoutlibCallback layoutlibCallback) {
86         assert Resources.mSystem == null  :
87                 "Resources_Delegate.initSystem called twice before disposeSystem was called";
88         Resources resources = new Resources(Resources_Delegate.class.getClassLoader());
89         resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments()));
90         resources.getConfiguration().windowConfiguration.setMaxBounds(0, 0, metrics.widthPixels,
91                 metrics.heightPixels);
92         sContexts.put(resources, Objects.requireNonNull(context));
93         sLayoutlibCallbacks.put(resources, Objects.requireNonNull(layoutlibCallback));
94         return Resources.mSystem = resources;
95     }
96 
97     /** Returns the {@link BridgeContext} associated to the given {@link Resources} */
98     @VisibleForTesting
99     @NonNull
getContext(@onNull Resources resources)100     public static BridgeContext getContext(@NonNull Resources resources) {
101         assert sContexts.containsKey(resources) :
102                 "Resources_Delegate.getContext called before initSystem";
103         return sContexts.get(resources);
104     }
105 
106     /** Returns the {@link LayoutlibCallback} associated to the given {@link Resources} */
107     @VisibleForTesting
108     @NonNull
getLayoutlibCallback(@onNull Resources resources)109     public static LayoutlibCallback getLayoutlibCallback(@NonNull Resources resources) {
110         assert sLayoutlibCallbacks.containsKey(resources) :
111                 "Resources_Delegate.getLayoutlibCallback called before initSystem";
112         return sLayoutlibCallbacks.get(resources);
113     }
114 
115     /**
116      * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects around that
117      * would prevent us from unloading the library.
118      */
disposeSystem()119     public static void disposeSystem() {
120         sDrawableCache.evictAll();
121         sContexts.clear();
122         sLayoutlibCallbacks.clear();
123         DrawableInflater_Delegate.clearConstructorCache();
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.create(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.second.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.second, getContext(resources), theme);
187 
188                 if (drawable == null) {
189                     throwException(resources, id);
190                     return null;
191                 }
192 
193                 if (key != null) {
194                     Drawable.ConstantState state = drawable.getConstantState();
195                     if (state != null) {
196                         sDrawableCache.put(key, state);
197                     }
198                 }
199             }
200 
201             return drawable;
202         }
203 
204         // id was not found or not resolved. Throw a NotFoundException.
205         throwException(resources, id);
206 
207         // this is not used since the method above always throws
208         return null;
209     }
210 
211     @LayoutlibDelegate
getColor(Resources resources, int id)212     static int getColor(Resources resources, int id) {
213         return getColor(resources, id, null);
214     }
215 
216     @LayoutlibDelegate
getColor(Resources resources, int id, Theme theme)217     static int getColor(Resources resources, int id, Theme theme) throws NotFoundException {
218         Pair<String, ResourceValue> value = getResourceValue(resources, id);
219 
220         if (value != null) {
221             ResourceValue resourceValue = value.second;
222             try {
223                 return ResourceHelper.getColor(resourceValue.getValue());
224             } catch (NumberFormatException e) {
225                 ColorStateList stateList = ResourceHelper.getColorStateList(resourceValue,
226                         getContext(resources), theme);
227                 if (stateList != null) {
228                     return stateList.getDefaultColor();
229                 }
230                 Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_FORMAT, resourceValue.getName() +
231                         " is neither a color value nor a color state list", null, null);
232                 return 0;
233             }
234         }
235 
236         throwException(resources, id);
237         return 0;
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.second,
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.second;
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.second;
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(ILayoutLog.TAG_RESOURCES_FORMAT,
376                                 "Integer resource array contains non-integer value: \"" + element +
377                                         "\"", null, null);
378                     } catch (IllegalArgumentException e) {
379                         Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_FORMAT,
380                                 "Integer resource array contains wrong color format: \"" + element +
381                                         "\"", null, null);
382                     }
383                 } else {
384                     Bridge.getLog().error(ILayoutLog.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(ILayoutLog.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(ILayoutLog.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.second;
430 
431             assert resValue != null;
432             if (resValue != null) {
433                 final ResourceType type = resValue.getResourceType();
434                 if (type != ResourceType.ARRAY) {
435                     Bridge.getLog().error(ILayoutLog.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(ILayoutLog.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.second;
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(ILayoutLog.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.second;
510 
511             try {
512                 return ResourceHelper.getXmlBlockParser(getContext(resources), value);
513             } catch (XmlPullParserException e) {
514                 Bridge.getLog().error(ILayoutLog.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         BridgeContext context = getContext(resources);
530         RenderResources renderResources = context.getRenderResources();
531         // Remove all themes, including default, to ensure theme attributes are not resolved
532         renderResources.getAllThemes().clear();
533         BridgeTypedArray ta = context.internalObtainStyledAttributes(set, attrs, 0, 0);
534         // Reset styles to only the default if present
535         renderResources.clearStyles();
536         return ta;
537     }
538 
539     @LayoutlibDelegate
obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet set, int[] attrs)540     static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet
541             set, int[] attrs) {
542         return Resources.obtainAttributes_Original(resources, theme, set, attrs);
543     }
544 
545     @LayoutlibDelegate
obtainTypedArray(Resources resources, int id)546     static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException {
547         BridgeContext context = getContext(resources);
548         ResourceReference reference = context.resolveId(id);
549         RenderResources renderResources = context.getRenderResources();
550         ResourceValue value = renderResources.getResolvedResource(reference);
551 
552         if (!(value instanceof ArrayResourceValue)) {
553             throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id));
554         }
555 
556         ArrayResourceValue arrayValue = (ArrayResourceValue) value;
557         int length = arrayValue.getElementCount();
558         ResourceNamespace namespace = arrayValue.getNamespace();
559         BridgeTypedArray typedArray = newTypeArray(resources, length);
560 
561         for (int i = 0; i < length; i++) {
562             ResourceValue elementValue;
563             ResourceUrl resourceUrl = ResourceUrl.parse(arrayValue.getElement(i));
564             if (resourceUrl != null) {
565                 ResourceReference elementRef =
566                   resourceUrl.resolve(namespace, arrayValue.getNamespaceResolver());
567                 elementValue = renderResources.getResolvedResource(elementRef);
568             } else {
569                 elementValue = new ResourceValueImpl(namespace, ResourceType.STRING, "element" + i,
570                   arrayValue.getElement(i));
571             }
572             typedArray.bridgeSetValue(i, elementValue.getName(), namespace, i, elementValue);
573         }
574 
575         typedArray.sealArray();
576         return typedArray;
577     }
578 
579     @LayoutlibDelegate
getDimension(Resources resources, int id)580     static float getDimension(Resources resources, int id) throws NotFoundException {
581         Pair<String, ResourceValue> value = getResourceValue(resources, id);
582 
583         if (value != null) {
584             ResourceValue resValue = value.second;
585 
586             assert resValue != null;
587             if (resValue != null) {
588                 String v = resValue.getValue();
589                 if (v != null) {
590                     if (v.equals(BridgeConstants.MATCH_PARENT) ||
591                             v.equals(BridgeConstants.FILL_PARENT)) {
592                         return LayoutParams.MATCH_PARENT;
593                     } else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
594                         return LayoutParams.WRAP_CONTENT;
595                     }
596                     TypedValue tmpValue = new TypedValue();
597                     if (ResourceHelper.parseFloatAttribute(
598                             value.first, v, tmpValue, true /*requireUnit*/) &&
599                             tmpValue.type == TypedValue.TYPE_DIMENSION) {
600                         return tmpValue.getDimension(resources.getDisplayMetrics());
601                     }
602                 }
603             }
604         }
605 
606         // id was not found or not resolved. Throw a NotFoundException.
607         throwException(resources, id);
608 
609         // this is not used since the method above always throws
610         return 0;
611     }
612 
613     @LayoutlibDelegate
getDimensionPixelOffset(Resources resources, int id)614     static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException {
615         Pair<String, ResourceValue> value = getResourceValue(resources, id);
616 
617         if (value != null) {
618             ResourceValue resValue = value.second;
619 
620             assert resValue != null;
621             if (resValue != null) {
622                 String v = resValue.getValue();
623                 if (v != null) {
624                     TypedValue tmpValue = new TypedValue();
625                     if (ResourceHelper.parseFloatAttribute(
626                             value.first, v, tmpValue, true /*requireUnit*/) &&
627                             tmpValue.type == TypedValue.TYPE_DIMENSION) {
628                         return TypedValue.complexToDimensionPixelOffset(tmpValue.data,
629                                 resources.getDisplayMetrics());
630                     }
631                 }
632             }
633         }
634 
635         // id was not found or not resolved. Throw a NotFoundException.
636         throwException(resources, id);
637 
638         // this is not used since the method above always throws
639         return 0;
640     }
641 
642     @LayoutlibDelegate
getDimensionPixelSize(Resources resources, int id)643     static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException {
644         Pair<String, ResourceValue> value = getResourceValue(resources, id);
645 
646         if (value != null) {
647             ResourceValue resValue = value.second;
648 
649             assert resValue != null;
650             if (resValue != null) {
651                 String v = resValue.getValue();
652                 if (v != null) {
653                     TypedValue tmpValue = new TypedValue();
654                     if (ResourceHelper.parseFloatAttribute(
655                             value.first, v, tmpValue, true /*requireUnit*/) &&
656                             tmpValue.type == TypedValue.TYPE_DIMENSION) {
657                         return TypedValue.complexToDimensionPixelSize(tmpValue.data,
658                                 resources.getDisplayMetrics());
659                     }
660                 }
661             }
662         }
663 
664         // id was not found or not resolved. Throw a NotFoundException.
665         throwException(resources, id);
666 
667         // this is not used since the method above always throws
668         return 0;
669     }
670 
671     @LayoutlibDelegate
getInteger(Resources resources, int id)672     static int getInteger(Resources resources, int id) throws NotFoundException {
673         Pair<String, ResourceValue> value = getResourceValue(resources, id);
674 
675         if (value != null) {
676             ResourceValue resValue = value.second;
677 
678             assert resValue != null;
679             if (resValue != null) {
680                 String v = resValue.getValue();
681                 if (v != null) {
682                     try {
683                         return getInt(v);
684                     } catch (NumberFormatException e) {
685                         // return exception below
686                     }
687                 }
688             }
689         }
690 
691         // id was not found or not resolved. Throw a NotFoundException.
692         throwException(resources, id);
693 
694         // this is not used since the method above always throws
695         return 0;
696     }
697 
698     @LayoutlibDelegate
getFloat(Resources resources, int id)699     static float getFloat(Resources resources, int id) {
700         Pair<String, ResourceValue> value = getResourceValue(resources, id);
701 
702         if (value != null) {
703             ResourceValue resValue = value.second;
704 
705             if (resValue != null) {
706                 String v = resValue.getValue();
707                 if (v != null) {
708                     try {
709                         return Float.parseFloat(v);
710                     } catch (NumberFormatException ignore) {
711                     }
712                 }
713             }
714         }
715         return 0;
716     }
717 
718     @LayoutlibDelegate
getBoolean(Resources resources, int id)719     static boolean getBoolean(Resources resources, int id) throws NotFoundException {
720         Pair<String, ResourceValue> value = getResourceValue(resources, id);
721 
722         if (value != null) {
723             ResourceValue resValue = value.second;
724 
725             if (resValue != null) {
726                 String v = resValue.getValue();
727                 if (v != null) {
728                     return Boolean.parseBoolean(v);
729                 }
730             }
731         }
732 
733         // id was not found or not resolved. Throw a NotFoundException.
734         throwException(resources, id);
735 
736         // this is not used since the method above always throws
737         return false;
738     }
739 
740     @LayoutlibDelegate
getResourceEntryName(Resources resources, int resid)741     static String getResourceEntryName(Resources resources, int resid) throws NotFoundException {
742         ResourceReference resourceInfo = getResourceInfo(resources, resid);
743         if (resourceInfo != null) {
744             return resourceInfo.getName();
745         }
746         throwException(resid, null);
747         return null;
748     }
749 
750     @LayoutlibDelegate
getResourceName(Resources resources, int resid)751     static String getResourceName(Resources resources, int resid) throws NotFoundException {
752         ResourceReference resourceInfo = getResourceInfo(resources, resid);
753         if (resourceInfo != null) {
754             String packageName = getPackageName(resourceInfo, resources);
755             return packageName + ':' + resourceInfo.getResourceType().getName() + '/' +
756                     resourceInfo.getName();
757         }
758         throwException(resid, null);
759         return null;
760     }
761 
762     @LayoutlibDelegate
getResourcePackageName(Resources resources, int resid)763     static String getResourcePackageName(Resources resources, int resid) throws NotFoundException {
764         ResourceReference resourceInfo = getResourceInfo(resources, resid);
765         if (resourceInfo != null) {
766             return getPackageName(resourceInfo, resources);
767         }
768         throwException(resid, null);
769         return null;
770     }
771 
772     @LayoutlibDelegate
getResourceTypeName(Resources resources, int resid)773     static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
774         ResourceReference resourceInfo = getResourceInfo(resources, resid);
775         if (resourceInfo != null) {
776             return resourceInfo.getResourceType().getName();
777         }
778         throwException(resid, null);
779         return null;
780     }
781 
getPackageName(ResourceReference resourceInfo, Resources resources)782     private static String getPackageName(ResourceReference resourceInfo, Resources resources) {
783         String packageName = resourceInfo.getNamespace().getPackageName();
784         if (packageName == null) {
785             packageName = getLayoutlibCallback(resources).getResourcePackage();
786             if (packageName == null) {
787                 packageName = APP_PREFIX;
788             }
789         }
790         return packageName;
791     }
792 
793     @LayoutlibDelegate
getString(Resources resources, int id, Object... formatArgs)794     static String getString(Resources resources, int id, Object... formatArgs)
795             throws NotFoundException {
796         String s = getString(resources, id);
797         if (s != null) {
798             return String.format(s, formatArgs);
799 
800         }
801 
802         // id was not found or not resolved. Throw a NotFoundException.
803         throwException(resources, id);
804 
805         // this is not used since the method above always throws
806         return null;
807     }
808 
809     @LayoutlibDelegate
getString(Resources resources, int id)810     static String getString(Resources resources, int id) throws NotFoundException {
811         Pair<String, ResourceValue> value = getResourceValue(resources, id);
812 
813         if (value != null && value.second.getValue() != null) {
814             return value.second.getValue();
815         }
816 
817         // id was not found or not resolved. Throw a NotFoundException.
818         throwException(resources, id);
819 
820         // this is not used since the method above always throws
821         return null;
822     }
823 
824     @LayoutlibDelegate
getQuantityString(Resources resources, int id, int quantity)825     static String getQuantityString(Resources resources, int id, int quantity) throws
826             NotFoundException {
827         Pair<String, ResourceValue> value = getResourceValue(resources, id);
828 
829         if (value != null) {
830             if (value.second instanceof PluralsResourceValue) {
831                 PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.second;
832                 PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales()
833                         .get(0));
834                 String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity));
835                 if (strValue == null) {
836                     strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER);
837                 }
838 
839                 return strValue;
840             }
841             else {
842                 return value.second.getValue();
843             }
844         }
845 
846         // id was not found or not resolved. Throw a NotFoundException.
847         throwException(resources, id);
848 
849         // this is not used since the method above always throws
850         return null;
851     }
852 
853     @LayoutlibDelegate
getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)854     static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)
855             throws NotFoundException {
856         String raw = getQuantityString(resources, id, quantity);
857         return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs);
858     }
859 
860     @LayoutlibDelegate
getQuantityText(Resources resources, int id, int quantity)861     static CharSequence getQuantityText(Resources resources, int id, int quantity) throws
862             NotFoundException {
863         return getQuantityString(resources, id, quantity);
864     }
865 
866     @LayoutlibDelegate
getFont(Resources resources, int id)867     static Typeface getFont(Resources resources, int id) throws
868             NotFoundException {
869         Pair<String, ResourceValue> value = getResourceValue(resources, id);
870         if (value != null) {
871             return ResourceHelper.getFont(value.second, getContext(resources), null);
872         }
873 
874         throwException(resources, id);
875 
876         // this is not used since the method above always throws
877         return null;
878     }
879 
880     @LayoutlibDelegate
getFont(Resources resources, TypedValue outValue, int id)881     static Typeface getFont(Resources resources, TypedValue outValue, int id) throws
882             NotFoundException {
883         ResourceValue resVal = getResourceValue(resources, id, outValue);
884         if (resVal != null) {
885             return ResourceHelper.getFont(resVal, getContext(resources), null);
886         }
887 
888         throwException(resources, id);
889         return null; // This is not used since the method above always throws.
890     }
891 
892     @LayoutlibDelegate
getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)893     static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
894             throws NotFoundException {
895         getResourceValue(resources, id, outValue);
896     }
897 
getResourceValue(Resources resources, int id, TypedValue outValue)898     private static ResourceValue getResourceValue(Resources resources, int id, TypedValue outValue)
899             throws NotFoundException {
900         Pair<String, ResourceValue> value = getResourceValue(resources, id);
901 
902         if (value != null) {
903             ResourceValue resVal = value.second;
904             String v = resVal != null ? resVal.getValue() : null;
905 
906             if (v != null) {
907                 if (ResourceHelper.parseFloatAttribute(value.first, v, outValue,
908                         false /*requireUnit*/)) {
909                     return resVal;
910                 }
911                 if (resVal instanceof DensityBasedResourceValue) {
912                     outValue.density =
913                             ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
914                 }
915 
916                 // else it's a string
917                 outValue.type = TypedValue.TYPE_STRING;
918                 outValue.string = v;
919                 return resVal;
920             }
921         }
922 
923         // id was not found or not resolved. Throw a NotFoundException.
924         throwException(resources, id);
925         return null; // This is not used since the method above always throws.
926     }
927 
928     @LayoutlibDelegate
getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)929     static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)
930             throws NotFoundException {
931         throw new UnsupportedOperationException();
932     }
933 
934     @LayoutlibDelegate
getValueForDensity(Resources resources, int id, int density, TypedValue outValue, boolean resolveRefs)935     static void getValueForDensity(Resources resources, int id, int density, TypedValue outValue,
936             boolean resolveRefs) throws NotFoundException {
937         getValue(resources, id, outValue, resolveRefs);
938     }
939 
940     @LayoutlibDelegate
getAttributeSetSourceResId(@ullable AttributeSet set)941     static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
942         // Not supported in layoutlib
943         return Resources.ID_NULL;
944     }
945 
946     @LayoutlibDelegate
getXml(Resources resources, int id)947     static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
948         Pair<String, ResourceValue> v = getResourceValue(resources, id);
949 
950         if (v != null) {
951             ResourceValue value = v.second;
952 
953             try {
954                 return ResourceHelper.getXmlBlockParser(getContext(resources), value);
955             } catch (XmlPullParserException e) {
956                 Bridge.getLog().error(ILayoutLog.TAG_BROKEN,
957                         "Failed to parse " + value.getValue(), e, null, null /*data*/);
958                 // we'll return null below.
959             }
960         }
961 
962         // id was not found or not resolved. Throw a NotFoundException.
963         throwException(resources, id);
964 
965         // this is not used since the method above always throws
966         return null;
967     }
968 
969     @LayoutlibDelegate
loadXmlResourceParser(Resources resources, int id, String type)970     static XmlResourceParser loadXmlResourceParser(Resources resources, int id,
971             String type) throws NotFoundException {
972         return resources.loadXmlResourceParser_Original(id, type);
973     }
974 
975     @LayoutlibDelegate
loadXmlResourceParser(Resources resources, String file, int id, int assetCookie, String type)976     static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id,
977             int assetCookie, String type) throws NotFoundException {
978         // even though we know the XML file to load directly, we still need to resolve the
979         // id so that we can know if it's a platform or project resource.
980         // (mPlatformResouceFlag will get the result and will be used later).
981         Pair<String, ResourceValue> result = getResourceValue(resources, id);
982 
983         ResourceNamespace layoutNamespace;
984         if (result != null && result.second != null) {
985             layoutNamespace = result.second.getNamespace();
986         } else {
987             // We need to pick something, even though the resource system never heard about a layout
988             // with this numeric id.
989             layoutNamespace = ResourceNamespace.RES_AUTO;
990         }
991 
992         try {
993             XmlPullParser parser = ParserFactory.create(file);
994             return new BridgeXmlBlockParser(parser, getContext(resources), layoutNamespace);
995         } catch (XmlPullParserException e) {
996             NotFoundException newE = new NotFoundException();
997             newE.initCause(e);
998             throw newE;
999         }
1000     }
1001 
1002     @LayoutlibDelegate
openRawResource(Resources resources, int id)1003     static InputStream openRawResource(Resources resources, int id) throws NotFoundException {
1004         Pair<String, ResourceValue> value = getResourceValue(resources, id);
1005 
1006         if (value != null) {
1007             String path = value.second.getValue();
1008             if (path != null) {
1009                 return openRawResource(resources, path);
1010             }
1011         }
1012 
1013         // id was not found or not resolved. Throw a NotFoundException.
1014         throwException(resources, id);
1015 
1016         // this is not used since the method above always throws
1017         return null;
1018     }
1019 
1020     @LayoutlibDelegate
openRawResource(Resources resources, int id, TypedValue value)1021     static InputStream openRawResource(Resources resources, int id, TypedValue value)
1022             throws NotFoundException {
1023         getValue(resources, id, value, true);
1024 
1025         String path = value.string.toString();
1026         return openRawResource(resources, path);
1027     }
1028 
openRawResource(Resources resources, String path)1029     private static InputStream openRawResource(Resources resources, String path)
1030             throws NotFoundException {
1031         AssetRepository repository = getAssetRepository(resources);
1032         try {
1033             InputStream stream = repository.openNonAsset(0, path, ACCESS_STREAMING);
1034             if (stream == null) {
1035                 throw new NotFoundException(path);
1036             }
1037             if (path.endsWith(".9.png")) {
1038                 stream = new NinePatchInputStream(stream, path);
1039             }
1040             return stream;
1041         } catch (IOException e) {
1042             NotFoundException exception = new NotFoundException();
1043             exception.initCause(e);
1044             throw exception;
1045         }
1046     }
1047 
1048     @LayoutlibDelegate
openRawResourceFd(Resources resources, int id)1049     static AssetFileDescriptor openRawResourceFd(Resources resources, int id)
1050             throws NotFoundException {
1051         throw new UnsupportedOperationException();
1052     }
1053 
1054     @VisibleForTesting
1055     @Nullable
resourceUrlFromName( @onNull String name, @Nullable String defType, @Nullable String defPackage)1056     static ResourceUrl resourceUrlFromName(
1057             @NonNull String name, @Nullable String defType, @Nullable String defPackage) {
1058         int colonIdx = name.indexOf(':');
1059         int slashIdx = name.indexOf('/');
1060 
1061         if (colonIdx != -1 && slashIdx != -1) {
1062             // Easy case
1063             return ResourceUrl.parse(PREFIX_RESOURCE_REF + name);
1064         }
1065 
1066         if (colonIdx == -1 && slashIdx == -1) {
1067             if (defType == null) {
1068                 throw new IllegalArgumentException("name does not define a type an no defType was" +
1069                         " passed");
1070             }
1071 
1072             // It does not define package or type
1073             return ResourceUrl.parse(
1074                     PREFIX_RESOURCE_REF + (defPackage != null ? defPackage + ":" : "") + defType +
1075                             "/" + name);
1076         }
1077 
1078         if (colonIdx != -1) {
1079             if (defType == null) {
1080                 throw new IllegalArgumentException("name does not define a type an no defType was" +
1081                         " passed");
1082             }
1083             // We have package but no type
1084             String pkg = name.substring(0, colonIdx);
1085             ResourceType type = ResourceType.fromClassName(defType);
1086             return type != null ? ResourceUrl.create(pkg, type, name.substring(colonIdx + 1)) :
1087                     null;
1088         }
1089 
1090         ResourceType type = ResourceType.fromClassName(name.substring(0, slashIdx));
1091         if (type == null) {
1092             return null;
1093         }
1094         // We have type but no package
1095         return ResourceUrl.create(defPackage,
1096                 type,
1097                 name.substring(slashIdx + 1));
1098     }
1099 
1100     @LayoutlibDelegate
getIdentifier(Resources resources, String name, String defType, String defPackage)1101     static int getIdentifier(Resources resources, String name, String defType, String defPackage) {
1102         if (name == null) {
1103             return 0;
1104         }
1105 
1106         ResourceUrl url = resourceUrlFromName(name, defType, defPackage);
1107         if (url != null) {
1108             if (ANDROID_PKG.equals(url.namespace)) {
1109                 return Bridge.getResourceId(url.type, url.name);
1110             }
1111 
1112             if (getLayoutlibCallback(resources).getResourcePackage().equals(url.namespace)) {
1113                 return getLayoutlibCallback(resources).getOrGenerateResourceId(
1114                         new ResourceReference(ResourceNamespace.RES_AUTO, url.type, url.name));
1115             }
1116         }
1117 
1118         return 0;
1119     }
1120 
1121     /**
1122      * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource
1123      * type.
1124      *
1125      * @param id the id of the resource
1126      * @param expectedType the type of resource that was expected
1127      *
1128      * @throws NotFoundException
1129      */
throwException(Resources resources, int id, @Nullable String expectedType)1130     private static void throwException(Resources resources, int id, @Nullable String expectedType)
1131             throws NotFoundException {
1132         throwException(id, getResourceInfo(resources, id), expectedType);
1133     }
1134 
throwException(Resources resources, int id)1135     private static void throwException(Resources resources, int id) throws NotFoundException {
1136         throwException(resources, id, null);
1137     }
1138 
throwException(int id, @Nullable ResourceReference resourceInfo)1139     private static void throwException(int id, @Nullable ResourceReference resourceInfo) {
1140         throwException(id, resourceInfo, null);
1141     }
throwException(int id, @Nullable ResourceReference resourceInfo, @Nullable String expectedType)1142     private static void throwException(int id, @Nullable ResourceReference resourceInfo,
1143             @Nullable String expectedType) {
1144         String message;
1145         if (resourceInfo != null) {
1146             message = String.format(
1147                     "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
1148                     resourceInfo.getResourceType(), id, resourceInfo.getName());
1149         } else {
1150             message = String.format("Could not resolve resource value: 0x%1$X.", id);
1151         }
1152 
1153         if (expectedType != null) {
1154             message += " Or the resolved value was not of type " + expectedType + " as expected.";
1155         }
1156         throw new NotFoundException(message);
1157     }
1158 
getInt(String v)1159     private static int getInt(String v) throws NumberFormatException {
1160         int radix = 10;
1161         if (v.startsWith("0x")) {
1162             v = v.substring(2);
1163             radix = 16;
1164         } else if (v.startsWith("0")) {
1165             radix = 8;
1166         }
1167         return Integer.parseInt(v, radix);
1168     }
1169 
getAssetRepository(Resources resources)1170     private static AssetRepository getAssetRepository(Resources resources) {
1171         BridgeContext context = getContext(resources);
1172         BridgeAssetManager assetManager = context.getAssets();
1173         return assetManager.getAssetRepository();
1174     }
1175 }
1176