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