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