BridgeResources.java revision 162c1dcd5e635d17d8425936d7729d0ae5ed1a62
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
17package android.content.res;
18
19import com.android.ide.common.rendering.api.IProjectCallback;
20import com.android.ide.common.rendering.api.LayoutLog;
21import com.android.ide.common.rendering.api.ResourceValue;
22import com.android.layoutlib.bridge.Bridge;
23import com.android.layoutlib.bridge.BridgeConstants;
24import com.android.layoutlib.bridge.android.BridgeContext;
25import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
26import com.android.layoutlib.bridge.impl.ParserFactory;
27import com.android.layoutlib.bridge.impl.ResourceHelper;
28import com.android.ninepatch.NinePatch;
29import com.android.resources.ResourceType;
30import com.android.util.Pair;
31
32import org.xmlpull.v1.XmlPullParser;
33import org.xmlpull.v1.XmlPullParserException;
34
35import android.graphics.drawable.Drawable;
36import android.util.AttributeSet;
37import android.util.DisplayMetrics;
38import android.util.TypedValue;
39import android.view.ViewGroup.LayoutParams;
40
41import java.io.File;
42import java.io.FileInputStream;
43import java.io.FileNotFoundException;
44import java.io.InputStream;
45
46/**
47 *
48 */
49public final class BridgeResources extends Resources {
50
51    private BridgeContext mContext;
52    private IProjectCallback mProjectCallback;
53    private boolean[] mPlatformResourceFlag = new boolean[1];
54
55    /**
56     * Simpler wrapper around FileInputStream. This is used when the input stream represent
57     * not a normal bitmap but a nine patch.
58     * This is useful when the InputStream is created in a method but used in another that needs
59     * to know whether this is 9-patch or not, such as BitmapFactory.
60     */
61    public class NinePatchInputStream extends FileInputStream {
62        private boolean mFakeMarkSupport = true;
63        public NinePatchInputStream(File file) throws FileNotFoundException {
64            super(file);
65        }
66
67        @Override
68        public boolean markSupported() {
69            if (mFakeMarkSupport) {
70                // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
71                return true;
72            }
73
74            return super.markSupported();
75        }
76
77        public void disableFakeMarkSupport() {
78            // disable fake mark support so that in case codec actually try to use them
79            // we don't lie to them.
80            mFakeMarkSupport = false;
81        }
82    }
83
84    /**
85     * This initializes the static field {@link Resources#mSystem} which is used
86     * by methods who get global resources using {@link Resources#getSystem()}.
87     * <p/>
88     * They will end up using our bridge resources.
89     * <p/>
90     * {@link Bridge} calls this method after setting up a new bridge.
91     */
92    public static Resources initSystem(BridgeContext context,
93            AssetManager assets,
94            DisplayMetrics metrics,
95            Configuration config,
96            IProjectCallback projectCallback) {
97        return Resources.mSystem = new BridgeResources(context,
98                assets,
99                metrics,
100                config,
101                projectCallback);
102    }
103
104    /**
105     * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects
106     * around that would prevent us from unloading the library.
107     */
108    public static void disposeSystem() {
109        if (Resources.mSystem instanceof BridgeResources) {
110            ((BridgeResources)(Resources.mSystem)).mContext = null;
111            ((BridgeResources)(Resources.mSystem)).mProjectCallback = null;
112        }
113        Resources.mSystem = null;
114    }
115
116    private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics,
117            Configuration config, IProjectCallback projectCallback) {
118        super(assets, metrics, config);
119        mContext = context;
120        mProjectCallback = projectCallback;
121    }
122
123    public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile,
124            boolean platformStyleable, String styleableName) {
125        return new BridgeTypedArray(this, mContext, numEntries, platformFile,
126                platformStyleable, styleableName);
127    }
128
129    private Pair<String, ResourceValue> getResourceValue(int id, boolean[] platformResFlag_out) {
130        // first get the String related to this id in the framework
131        Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
132
133        if (resourceInfo != null) {
134            platformResFlag_out[0] = true;
135            String attributeName = resourceInfo.getSecond();
136
137            return Pair.of(attributeName, mContext.getRenderResources().getFrameworkResource(
138                    resourceInfo.getFirst(), attributeName));
139        }
140
141        // didn't find a match in the framework? look in the project.
142        if (mProjectCallback != null) {
143            resourceInfo = mProjectCallback.resolveResourceId(id);
144
145            if (resourceInfo != null) {
146                platformResFlag_out[0] = false;
147                String attributeName = resourceInfo.getSecond();
148
149                return Pair.of(attributeName, mContext.getRenderResources().getProjectResource(
150                        resourceInfo.getFirst(), attributeName));
151            }
152        }
153
154        return null;
155    }
156
157    @Override
158    public Drawable getDrawable(int id) throws NotFoundException {
159        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
160
161        if (value != null) {
162            return ResourceHelper.getDrawable(value.getSecond(), mContext);
163        }
164
165        // id was not found or not resolved. Throw a NotFoundException.
166        throwException(id);
167
168        // this is not used since the method above always throws
169        return null;
170    }
171
172    @Override
173    public int getColor(int id) throws NotFoundException {
174        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
175
176        if (value != null) {
177            try {
178                return ResourceHelper.getColor(value.getSecond().getValue());
179            } catch (NumberFormatException e) {
180                Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e,
181                        null /*data*/);
182                return 0;
183            }
184        }
185
186        // id was not found or not resolved. Throw a NotFoundException.
187        throwException(id);
188
189        // this is not used since the method above always throws
190        return 0;
191    }
192
193    @Override
194    public ColorStateList getColorStateList(int id) throws NotFoundException {
195        Pair<String, ResourceValue> resValue = getResourceValue(id, mPlatformResourceFlag);
196
197        if (resValue != null) {
198            ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
199                    mContext);
200            if (stateList != null) {
201                return stateList;
202            }
203        }
204
205        // id was not found or not resolved. Throw a NotFoundException.
206        throwException(id);
207
208        // this is not used since the method above always throws
209        return null;
210    }
211
212    @Override
213    public CharSequence getText(int id) throws NotFoundException {
214        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
215
216        if (value != null) {
217            return value.getSecond().getValue();
218        }
219
220        // id was not found or not resolved. Throw a NotFoundException.
221        throwException(id);
222
223        // this is not used since the method above always throws
224        return null;
225    }
226
227    @Override
228    public XmlResourceParser getLayout(int id) throws NotFoundException {
229        Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
230
231        if (v != null) {
232            ResourceValue value = v.getSecond();
233            XmlPullParser parser = null;
234
235            try {
236                // check if the current parser can provide us with a custom parser.
237                if (mPlatformResourceFlag[0] == false) {
238                    parser = mProjectCallback.getParser(value);
239                }
240
241                // create a new one manually if needed.
242                if (parser == null) {
243                    File xml = new File(value.getValue());
244                    if (xml.isFile()) {
245                        // we need to create a pull parser around the layout XML file, and then
246                        // give that to our XmlBlockParser
247                        parser = ParserFactory.create(xml);
248                    }
249                }
250
251                if (parser != null) {
252                    return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
253                }
254            } catch (XmlPullParserException e) {
255                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
256                        "Failed to configure parser for " + value.getValue(), e, null /*data*/);
257                // we'll return null below.
258            } catch (FileNotFoundException e) {
259                // this shouldn't happen since we check above.
260            }
261
262        }
263
264        // id was not found or not resolved. Throw a NotFoundException.
265        throwException(id);
266
267        // this is not used since the method above always throws
268        return null;
269    }
270
271    @Override
272    public XmlResourceParser getAnimation(int id) throws NotFoundException {
273        Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
274
275        if (v != null) {
276            ResourceValue value = v.getSecond();
277            XmlPullParser parser = null;
278
279            try {
280                File xml = new File(value.getValue());
281                if (xml.isFile()) {
282                    // we need to create a pull parser around the layout XML file, and then
283                    // give that to our XmlBlockParser
284                    parser = ParserFactory.create(xml);
285
286                    return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
287                }
288            } catch (XmlPullParserException e) {
289                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
290                        "Failed to configure parser for " + value.getValue(), e, null /*data*/);
291                // we'll return null below.
292            } catch (FileNotFoundException e) {
293                // this shouldn't happen since we check above.
294            }
295
296        }
297
298        // id was not found or not resolved. Throw a NotFoundException.
299        throwException(id);
300
301        // this is not used since the method above always throws
302        return null;
303    }
304
305    @Override
306    public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
307        return mContext.obtainStyledAttributes(set, attrs);
308    }
309
310    @Override
311    public TypedArray obtainTypedArray(int id) throws NotFoundException {
312        throw new UnsupportedOperationException();
313    }
314
315
316    @Override
317    public float getDimension(int id) throws NotFoundException {
318        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
319
320        if (value != null) {
321            String v = value.getSecond().getValue();
322
323            if (v != null) {
324                if (v.equals(BridgeConstants.MATCH_PARENT) ||
325                        v.equals(BridgeConstants.FILL_PARENT)) {
326                    return LayoutParams.MATCH_PARENT;
327                } else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
328                    return LayoutParams.WRAP_CONTENT;
329                }
330
331                if (ResourceHelper.parseFloatAttribute(
332                        value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
333                        mTmpValue.type == TypedValue.TYPE_DIMENSION) {
334                    return mTmpValue.getDimension(getDisplayMetrics());
335                }
336            }
337        }
338
339        // id was not found or not resolved. Throw a NotFoundException.
340        throwException(id);
341
342        // this is not used since the method above always throws
343        return 0;
344    }
345
346    @Override
347    public int getDimensionPixelOffset(int id) throws NotFoundException {
348        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
349
350        if (value != null) {
351            String v = value.getSecond().getValue();
352
353            if (v != null) {
354                if (ResourceHelper.parseFloatAttribute(
355                        value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
356                        mTmpValue.type == TypedValue.TYPE_DIMENSION) {
357                    return TypedValue.complexToDimensionPixelOffset(mTmpValue.data,
358                            getDisplayMetrics());
359                }
360            }
361        }
362
363        // id was not found or not resolved. Throw a NotFoundException.
364        throwException(id);
365
366        // this is not used since the method above always throws
367        return 0;
368    }
369
370    @Override
371    public int getDimensionPixelSize(int id) throws NotFoundException {
372        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
373
374        if (value != null) {
375            String v = value.getSecond().getValue();
376
377            if (v != null) {
378                if (ResourceHelper.parseFloatAttribute(
379                        value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
380                        mTmpValue.type == TypedValue.TYPE_DIMENSION) {
381                    return TypedValue.complexToDimensionPixelSize(mTmpValue.data,
382                            getDisplayMetrics());
383                }
384            }
385        }
386
387        // id was not found or not resolved. Throw a NotFoundException.
388        throwException(id);
389
390        // this is not used since the method above always throws
391        return 0;
392    }
393
394    @Override
395    public int getInteger(int id) throws NotFoundException {
396        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
397
398        if (value != null && value.getSecond().getValue() != null) {
399            String v = value.getSecond().getValue();
400            int radix = 10;
401            if (v.startsWith("0x")) {
402                v = v.substring(2);
403                radix = 16;
404            }
405            try {
406                return Integer.parseInt(v, radix);
407            } catch (NumberFormatException e) {
408                // return exception below
409            }
410        }
411
412        // id was not found or not resolved. Throw a NotFoundException.
413        throwException(id);
414
415        // this is not used since the method above always throws
416        return 0;
417    }
418
419    @Override
420    public boolean getBoolean(int id) throws NotFoundException {
421        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
422
423        if (value != null && value.getSecond().getValue() != null) {
424            String v = value.getSecond().getValue();
425            return Boolean.parseBoolean(v);
426        }
427
428        // id was not found or not resolved. Throw a NotFoundException.
429        throwException(id);
430
431        // this is not used since the method above always throws
432        return false;
433    }
434
435    @Override
436    public String getResourceEntryName(int resid) throws NotFoundException {
437        throw new UnsupportedOperationException();
438    }
439
440    @Override
441    public String getResourceName(int resid) throws NotFoundException {
442        throw new UnsupportedOperationException();
443    }
444
445    @Override
446    public String getResourceTypeName(int resid) throws NotFoundException {
447        throw new UnsupportedOperationException();
448    }
449
450    @Override
451    public String getString(int id, Object... formatArgs) throws NotFoundException {
452        String s = getString(id);
453        if (s != null) {
454            return String.format(s, formatArgs);
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
466    public String getString(int id) throws NotFoundException {
467        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
468
469        if (value != null && value.getSecond().getValue() != null) {
470            return value.getSecond().getValue();
471        }
472
473        // id was not found or not resolved. Throw a NotFoundException.
474        throwException(id);
475
476        // this is not used since the method above always throws
477        return null;
478    }
479
480    @Override
481    public void getValue(int id, TypedValue outValue, boolean resolveRefs)
482            throws NotFoundException {
483        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
484
485        if (value != null) {
486            String v = value.getSecond().getValue();
487
488            if (v != null) {
489                if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
490                        false /*requireUnit*/)) {
491                    return;
492                }
493
494                // else it's a string
495                outValue.type = TypedValue.TYPE_STRING;
496                outValue.string = v;
497                return;
498            }
499        }
500
501        // id was not found or not resolved. Throw a NotFoundException.
502        throwException(id);
503    }
504
505    @Override
506    public void getValue(String name, TypedValue outValue, boolean resolveRefs)
507            throws NotFoundException {
508        throw new UnsupportedOperationException();
509    }
510
511    @Override
512    public XmlResourceParser getXml(int id) throws NotFoundException {
513        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
514
515        if (value != null) {
516            String v = value.getSecond().getValue();
517
518            if (v != null) {
519                // check this is a file
520                File f = new File(v);
521                if (f.isFile()) {
522                    try {
523                        XmlPullParser parser = ParserFactory.create(f);
524
525                        return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
526                    } catch (XmlPullParserException e) {
527                        NotFoundException newE = new NotFoundException();
528                        newE.initCause(e);
529                        throw newE;
530                    } catch (FileNotFoundException e) {
531                        NotFoundException newE = new NotFoundException();
532                        newE.initCause(e);
533                        throw newE;
534                    }
535                }
536            }
537        }
538
539        // id was not found or not resolved. Throw a NotFoundException.
540        throwException(id);
541
542        // this is not used since the method above always throws
543        return null;
544    }
545
546    @Override
547    public XmlResourceParser loadXmlResourceParser(String file, int id,
548            int assetCookie, String type) throws NotFoundException {
549        // even though we know the XML file to load directly, we still need to resolve the
550        // id so that we can know if it's a platform or project resource.
551        // (mPlatformResouceFlag will get the result and will be used later).
552        getResourceValue(id, mPlatformResourceFlag);
553
554        File f = new File(file);
555        try {
556            XmlPullParser parser = ParserFactory.create(f);
557
558            return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
559        } catch (XmlPullParserException e) {
560            NotFoundException newE = new NotFoundException();
561            newE.initCause(e);
562            throw newE;
563        } catch (FileNotFoundException e) {
564            NotFoundException newE = new NotFoundException();
565            newE.initCause(e);
566            throw newE;
567        }
568    }
569
570
571    @Override
572    public InputStream openRawResource(int id) throws NotFoundException {
573        Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
574
575        if (value != null) {
576            String path = value.getSecond().getValue();
577
578            if (path != null) {
579                // check this is a file
580                File f = new File(path);
581                if (f.isFile()) {
582                    try {
583                        // if it's a nine-patch return a custom input stream so that
584                        // other methods (mainly bitmap factory) can detect it's a 9-patch
585                        // and actually load it as a 9-patch instead of a normal bitmap
586                        if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
587                            return new NinePatchInputStream(f);
588                        }
589                        return new FileInputStream(f);
590                    } catch (FileNotFoundException e) {
591                        NotFoundException newE = new NotFoundException();
592                        newE.initCause(e);
593                        throw newE;
594                    }
595                }
596            }
597        }
598
599        // id was not found or not resolved. Throw a NotFoundException.
600        throwException(id);
601
602        // this is not used since the method above always throws
603        return null;
604    }
605
606    @Override
607    public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
608        getValue(id, value, true);
609
610        String path = value.string.toString();
611
612        File f = new File(path);
613        if (f.isFile()) {
614            try {
615                // if it's a nine-patch return a custom input stream so that
616                // other methods (mainly bitmap factory) can detect it's a 9-patch
617                // and actually load it as a 9-patch instead of a normal bitmap
618                if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
619                    return new NinePatchInputStream(f);
620                }
621                return new FileInputStream(f);
622            } catch (FileNotFoundException e) {
623                NotFoundException exception = new NotFoundException();
624                exception.initCause(e);
625                throw exception;
626            }
627        }
628
629        throw new NotFoundException();
630    }
631
632    @Override
633    public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
634        throw new UnsupportedOperationException();
635    }
636
637    /**
638     * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type.
639     * @param id the id of the resource
640     * @throws NotFoundException
641     */
642    private void throwException(int id) throws NotFoundException {
643        // first get the String related to this id in the framework
644        Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
645
646        // if the name is unknown in the framework, get it from the custom view loader.
647        if (resourceInfo == null && mProjectCallback != null) {
648            resourceInfo = mProjectCallback.resolveResourceId(id);
649        }
650
651        String message = null;
652        if (resourceInfo != null) {
653            message = String.format(
654                    "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
655                    resourceInfo.getFirst(), id, resourceInfo.getSecond());
656        } else {
657            message = String.format(
658                    "Could not resolve resource value: 0x%1$X.", id);
659        }
660
661        throw new NotFoundException(message);
662    }
663}
664