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