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