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