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