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