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
17package android.content.res;
18
19import com.android.SdkConstants;
20import com.android.ide.common.rendering.api.ArrayResourceValue;
21import com.android.ide.common.rendering.api.DensityBasedResourceValue;
22import com.android.ide.common.rendering.api.LayoutLog;
23import com.android.ide.common.rendering.api.LayoutlibCallback;
24import com.android.ide.common.rendering.api.RenderResources;
25import com.android.ide.common.rendering.api.ResourceValue;
26import com.android.layoutlib.bridge.Bridge;
27import com.android.layoutlib.bridge.BridgeConstants;
28import com.android.layoutlib.bridge.android.BridgeContext;
29import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
30import com.android.layoutlib.bridge.impl.ParserFactory;
31import com.android.layoutlib.bridge.impl.ResourceHelper;
32import com.android.layoutlib.bridge.util.NinePatchInputStream;
33import com.android.ninepatch.NinePatch;
34import com.android.resources.ResourceType;
35import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
36import com.android.util.Pair;
37
38import org.xmlpull.v1.XmlPullParser;
39import org.xmlpull.v1.XmlPullParserException;
40
41import android.annotation.NonNull;
42import android.annotation.Nullable;
43import android.content.res.Resources.NotFoundException;
44import android.content.res.Resources.Theme;
45import android.graphics.drawable.Drawable;
46import android.util.AttributeSet;
47import android.util.DisplayMetrics;
48import android.util.TypedValue;
49import android.view.ViewGroup.LayoutParams;
50
51import java.io.File;
52import java.io.FileInputStream;
53import java.io.FileNotFoundException;
54import java.io.InputStream;
55import java.util.Iterator;
56
57@SuppressWarnings("deprecation")
58public class Resources_Delegate {
59
60    private static boolean[] mPlatformResourceFlag = new boolean[1];
61
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     */
77    public static void disposeSystem() {
78        Resources.mSystem.mContext = null;
79        Resources.mSystem.mLayoutlibCallback = null;
80        Resources.mSystem = null;
81    }
82
83    public static BridgeTypedArray newTypeArray(Resources resources, int numEntries,
84            boolean platformFile) {
85        return new BridgeTypedArray(resources, resources.mContext, numEntries, platformFile);
86    }
87
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
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
133    static Drawable getDrawable(Resources resources, int id) {
134        return getDrawable(resources, id, null);
135    }
136
137    @LayoutlibDelegate
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
153    static int getColor(Resources resources, int id) {
154        return getColor(resources, id, null);
155    }
156
157    @LayoutlibDelegate
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
188    static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException {
189        return getColorStateList(resources, id, null);
190    }
191
192    @LayoutlibDelegate
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
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
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
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
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     */
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
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
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
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
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
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
479    static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) {
480        return resources.mContext.obtainStyledAttributes(set, attrs);
481    }
482
483    @LayoutlibDelegate
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
490    static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException {
491        throw new UnsupportedOperationException();
492    }
493
494    @LayoutlibDelegate
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
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
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
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
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
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
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
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
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
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
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
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
752    static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)
753            throws NotFoundException {
754        throw new UnsupportedOperationException();
755    }
756
757    @LayoutlibDelegate
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
794    static XmlResourceParser loadXmlResourceParser(Resources resources, int id,
795            String type) throws NotFoundException {
796        return resources.loadXmlResourceParser_Original(id, type);
797    }
798
799    @LayoutlibDelegate
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
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
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
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     */
899    private static void throwException(Resources resources, int id) throws NotFoundException {
900        throwException(id, getResourceInfo(resources, id, new boolean[1]));
901    }
902
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
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