blob: c3e06d201ada8f245288e1e6ded4fc7648d195e8 [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 {
158 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
159
160 if (value != null) {
161 return ResourceHelper.getDrawable(value.getSecond(), mContext);
162 }
163
164 // id was not found or not resolved. Throw a NotFoundException.
165 throwException(id);
166
167 // this is not used since the method above always throws
168 return null;
169 }
170
171 @Override
172 public int getColor(int id) throws NotFoundException {
173 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
174
175 if (value != null) {
176 try {
177 return ResourceHelper.getColor(value.getSecond().getValue());
178 } catch (NumberFormatException e) {
179 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e,
180 null /*data*/);
181 return 0;
182 }
183 }
184
185 // id was not found or not resolved. Throw a NotFoundException.
186 throwException(id);
187
188 // this is not used since the method above always throws
189 return 0;
190 }
191
192 @Override
193 public ColorStateList getColorStateList(int id) throws NotFoundException {
194 Pair<String, ResourceValue> resValue = getResourceValue(id, mPlatformResourceFlag);
195
196 if (resValue != null) {
197 ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
198 mContext);
199 if (stateList != null) {
200 return stateList;
201 }
202 }
203
204 // id was not found or not resolved. Throw a NotFoundException.
205 throwException(id);
206
207 // this is not used since the method above always throws
208 return null;
209 }
210
211 @Override
212 public CharSequence getText(int id) throws NotFoundException {
213 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
214
215 if (value != null) {
216 ResourceValue resValue = value.getSecond();
217
218 assert resValue != null;
219 if (resValue != null) {
220 String v = resValue.getValue();
221 if (v != null) {
222 return v;
223 }
224 }
225 }
226
227 // id was not found or not resolved. Throw a NotFoundException.
228 throwException(id);
229
230 // this is not used since the method above always throws
231 return null;
232 }
233
234 @Override
235 public XmlResourceParser getLayout(int id) throws NotFoundException {
236 Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
237
238 if (v != null) {
239 ResourceValue value = v.getSecond();
240 XmlPullParser parser = null;
241
242 try {
243 // check if the current parser can provide us with a custom parser.
244 if (mPlatformResourceFlag[0] == false) {
245 parser = mProjectCallback.getParser(value);
246 }
247
248 // create a new one manually if needed.
249 if (parser == null) {
250 File xml = new File(value.getValue());
251 if (xml.isFile()) {
252 // we need to create a pull parser around the layout XML file, and then
253 // give that to our XmlBlockParser
254 parser = ParserFactory.create(xml);
255 }
256 }
257
258 if (parser != null) {
259 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
260 }
261 } catch (XmlPullParserException e) {
262 Bridge.getLog().error(LayoutLog.TAG_BROKEN,
263 "Failed to configure parser for " + value.getValue(), e, null /*data*/);
264 // we'll return null below.
265 } catch (FileNotFoundException e) {
266 // this shouldn't happen since we check above.
267 }
268
269 }
270
271 // id was not found or not resolved. Throw a NotFoundException.
272 throwException(id);
273
274 // this is not used since the method above always throws
275 return null;
276 }
277
278 @Override
279 public XmlResourceParser getAnimation(int id) throws NotFoundException {
280 Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
281
282 if (v != null) {
283 ResourceValue value = v.getSecond();
284 XmlPullParser parser = null;
285
286 try {
287 File xml = new File(value.getValue());
288 if (xml.isFile()) {
289 // we need to create a pull parser around the layout XML file, and then
290 // give that to our XmlBlockParser
291 parser = ParserFactory.create(xml);
292
293 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
294 }
295 } catch (XmlPullParserException e) {
296 Bridge.getLog().error(LayoutLog.TAG_BROKEN,
297 "Failed to configure parser for " + value.getValue(), e, null /*data*/);
298 // we'll return null below.
299 } catch (FileNotFoundException e) {
300 // this shouldn't happen since we check above.
301 }
302
303 }
304
305 // id was not found or not resolved. Throw a NotFoundException.
306 throwException(id);
307
308 // this is not used since the method above always throws
309 return null;
310 }
311
312 @Override
313 public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
314 return mContext.obtainStyledAttributes(set, attrs);
315 }
316
317 @Override
318 public TypedArray obtainTypedArray(int id) throws NotFoundException {
319 throw new UnsupportedOperationException();
320 }
321
322
323 @Override
324 public float getDimension(int id) throws NotFoundException {
325 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
326
327 if (value != null) {
328 ResourceValue resValue = value.getSecond();
329
330 assert resValue != null;
331 if (resValue != null) {
332 String v = resValue.getValue();
333 if (v != null) {
334 if (v.equals(BridgeConstants.MATCH_PARENT) ||
335 v.equals(BridgeConstants.FILL_PARENT)) {
336 return LayoutParams.MATCH_PARENT;
337 } else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
338 return LayoutParams.WRAP_CONTENT;
339 }
340
341 if (ResourceHelper.parseFloatAttribute(
342 value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
343 mTmpValue.type == TypedValue.TYPE_DIMENSION) {
344 return mTmpValue.getDimension(getDisplayMetrics());
345 }
346 }
347 }
348 }
349
350 // id was not found or not resolved. Throw a NotFoundException.
351 throwException(id);
352
353 // this is not used since the method above always throws
354 return 0;
355 }
356
357 @Override
358 public int getDimensionPixelOffset(int id) throws NotFoundException {
359 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
360
361 if (value != null) {
362 ResourceValue resValue = value.getSecond();
363
364 assert resValue != null;
365 if (resValue != null) {
366 String v = resValue.getValue();
367 if (v != null) {
368 if (ResourceHelper.parseFloatAttribute(
369 value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
370 mTmpValue.type == TypedValue.TYPE_DIMENSION) {
371 return TypedValue.complexToDimensionPixelOffset(mTmpValue.data,
372 getDisplayMetrics());
373 }
374 }
375 }
376 }
377
378 // id was not found or not resolved. Throw a NotFoundException.
379 throwException(id);
380
381 // this is not used since the method above always throws
382 return 0;
383 }
384
385 @Override
386 public int getDimensionPixelSize(int id) throws NotFoundException {
387 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
388
389 if (value != null) {
390 ResourceValue resValue = value.getSecond();
391
392 assert resValue != null;
393 if (resValue != null) {
394 String v = resValue.getValue();
395 if (v != null) {
396 if (ResourceHelper.parseFloatAttribute(
397 value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
398 mTmpValue.type == TypedValue.TYPE_DIMENSION) {
399 return TypedValue.complexToDimensionPixelSize(mTmpValue.data,
400 getDisplayMetrics());
401 }
402 }
403 }
404 }
405
406 // id was not found or not resolved. Throw a NotFoundException.
407 throwException(id);
408
409 // this is not used since the method above always throws
410 return 0;
411 }
412
413 @Override
414 public int getInteger(int id) throws NotFoundException {
415 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
416
417 if (value != null) {
418 ResourceValue resValue = value.getSecond();
419
420 assert resValue != null;
421 if (resValue != null) {
422 String v = resValue.getValue();
423 if (v != null) {
424 int radix = 10;
425 if (v.startsWith("0x")) {
426 v = v.substring(2);
427 radix = 16;
428 }
429 try {
430 return Integer.parseInt(v, radix);
431 } catch (NumberFormatException e) {
432 // return exception below
433 }
434 }
435 }
436 }
437
438 // id was not found or not resolved. Throw a NotFoundException.
439 throwException(id);
440
441 // this is not used since the method above always throws
442 return 0;
443 }
444
445 @Override
446 public boolean getBoolean(int id) throws NotFoundException {
447 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
448
449 if (value != null) {
450 ResourceValue resValue = value.getSecond();
451
452 assert resValue != null;
453 if (resValue != null) {
454 String v = resValue.getValue();
455 if (v != null) {
456 return Boolean.parseBoolean(v);
457 }
458 }
459 }
460
461 // id was not found or not resolved. Throw a NotFoundException.
462 throwException(id);
463
464 // this is not used since the method above always throws
465 return false;
466 }
467
468 @Override
469 public String getResourceEntryName(int resid) throws NotFoundException {
470 throw new UnsupportedOperationException();
471 }
472
473 @Override
474 public String getResourceName(int resid) throws NotFoundException {
475 throw new UnsupportedOperationException();
476 }
477
478 @Override
479 public String getResourceTypeName(int resid) throws NotFoundException {
480 throw new UnsupportedOperationException();
481 }
482
483 @Override
484 public String getString(int id, Object... formatArgs) throws NotFoundException {
485 String s = getString(id);
486 if (s != null) {
487 return String.format(s, formatArgs);
488
489 }
490
491 // id was not found or not resolved. Throw a NotFoundException.
492 throwException(id);
493
494 // this is not used since the method above always throws
495 return null;
496 }
497
498 @Override
499 public String getString(int id) throws NotFoundException {
500 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
501
502 if (value != null && value.getSecond().getValue() != null) {
503 return value.getSecond().getValue();
504 }
505
506 // id was not found or not resolved. Throw a NotFoundException.
507 throwException(id);
508
509 // this is not used since the method above always throws
510 return null;
511 }
512
513 @Override
514 public void getValue(int id, TypedValue outValue, boolean resolveRefs)
515 throws NotFoundException {
516 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
517
518 if (value != null) {
519 String v = value.getSecond().getValue();
520
521 if (v != null) {
522 if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
523 false /*requireUnit*/)) {
524 return;
525 }
526
527 // else it's a string
528 outValue.type = TypedValue.TYPE_STRING;
529 outValue.string = v;
530 return;
531 }
532 }
533
534 // id was not found or not resolved. Throw a NotFoundException.
535 throwException(id);
536 }
537
538 @Override
539 public void getValue(String name, TypedValue outValue, boolean resolveRefs)
540 throws NotFoundException {
541 throw new UnsupportedOperationException();
542 }
543
544 @Override
545 public XmlResourceParser getXml(int id) throws NotFoundException {
546 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
547
548 if (value != null) {
549 String v = value.getSecond().getValue();
550
551 if (v != null) {
552 // check this is a file
553 File f = new File(v);
554 if (f.isFile()) {
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
572 // id was not found or not resolved. Throw a NotFoundException.
573 throwException(id);
574
575 // this is not used since the method above always throws
576 return null;
577 }
578
579 @Override
580 public XmlResourceParser loadXmlResourceParser(String file, int id,
581 int assetCookie, String type) throws NotFoundException {
582 // even though we know the XML file to load directly, we still need to resolve the
583 // id so that we can know if it's a platform or project resource.
584 // (mPlatformResouceFlag will get the result and will be used later).
585 getResourceValue(id, mPlatformResourceFlag);
586
587 File f = new File(file);
588 try {
589 XmlPullParser parser = ParserFactory.create(f);
590
591 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
592 } catch (XmlPullParserException e) {
593 NotFoundException newE = new NotFoundException();
594 newE.initCause(e);
595 throw newE;
596 } catch (FileNotFoundException e) {
597 NotFoundException newE = new NotFoundException();
598 newE.initCause(e);
599 throw newE;
600 }
601 }
602
603
604 @Override
605 public InputStream openRawResource(int id) throws NotFoundException {
606 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
607
608 if (value != null) {
609 String path = value.getSecond().getValue();
610
611 if (path != null) {
612 // check this is a file
613 File f = new File(path);
614 if (f.isFile()) {
615 try {
616 // if it's a nine-patch return a custom input stream so that
617 // other methods (mainly bitmap factory) can detect it's a 9-patch
618 // and actually load it as a 9-patch instead of a normal bitmap
619 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
620 return new NinePatchInputStream(f);
621 }
622 return new FileInputStream(f);
623 } catch (FileNotFoundException e) {
624 NotFoundException newE = new NotFoundException();
625 newE.initCause(e);
626 throw newE;
627 }
628 }
629 }
630 }
631
632 // id was not found or not resolved. Throw a NotFoundException.
633 throwException(id);
634
635 // this is not used since the method above always throws
636 return null;
637 }
638
639 @Override
640 public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
641 getValue(id, value, true);
642
643 String path = value.string.toString();
644
645 File f = new File(path);
646 if (f.isFile()) {
647 try {
648 // if it's a nine-patch return a custom input stream so that
649 // other methods (mainly bitmap factory) can detect it's a 9-patch
650 // and actually load it as a 9-patch instead of a normal bitmap
651 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
652 return new NinePatchInputStream(f);
653 }
654 return new FileInputStream(f);
655 } catch (FileNotFoundException e) {
656 NotFoundException exception = new NotFoundException();
657 exception.initCause(e);
658 throw exception;
659 }
660 }
661
662 throw new NotFoundException();
663 }
664
665 @Override
666 public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
667 throw new UnsupportedOperationException();
668 }
669
670 /**
671 * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type.
672 * @param id the id of the resource
673 * @throws NotFoundException
674 */
675 private void throwException(int id) throws NotFoundException {
676 // first get the String related to this id in the framework
677 Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
678
679 // if the name is unknown in the framework, get it from the custom view loader.
680 if (resourceInfo == null && mProjectCallback != null) {
681 resourceInfo = mProjectCallback.resolveResourceId(id);
682 }
683
684 String message = null;
685 if (resourceInfo != null) {
686 message = String.format(
687 "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
688 resourceInfo.getFirst(), id, resourceInfo.getSecond());
689 } else {
690 message = String.format(
691 "Could not resolve resource value: 0x%1$X.", id);
692 }
693
694 throw new NotFoundException(message);
695 }
696}