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