blob: 163fbcb7f9004aaec596f5459640486d290d65fd [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
Deepanshu Gupta642cff52015-05-22 15:47:16 -070019import com.android.SdkConstants;
20import com.android.ide.common.rendering.api.ArrayResourceValue;
Adam Lesinski282e1812014-01-23 18:17:42 -080021import com.android.ide.common.rendering.api.LayoutLog;
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -070022import com.android.ide.common.rendering.api.LayoutlibCallback;
Adam Lesinski282e1812014-01-23 18:17:42 -080023import com.android.ide.common.rendering.api.ResourceValue;
24import com.android.layoutlib.bridge.Bridge;
25import com.android.layoutlib.bridge.BridgeConstants;
26import com.android.layoutlib.bridge.android.BridgeContext;
27import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
28import com.android.layoutlib.bridge.impl.ParserFactory;
29import com.android.layoutlib.bridge.impl.ResourceHelper;
30import com.android.ninepatch.NinePatch;
31import com.android.resources.ResourceType;
32import com.android.util.Pair;
33
34import org.xmlpull.v1.XmlPullParser;
35import org.xmlpull.v1.XmlPullParserException;
36
Deepanshu Gupta642cff52015-05-22 15:47:16 -070037import android.annotation.NonNull;
38import android.annotation.Nullable;
Adam Lesinski282e1812014-01-23 18:17:42 -080039import android.graphics.drawable.Drawable;
40import android.util.AttributeSet;
41import android.util.DisplayMetrics;
42import android.util.TypedValue;
43import android.view.ViewGroup.LayoutParams;
44
45import java.io.File;
46import java.io.FileInputStream;
47import java.io.FileNotFoundException;
48import java.io.InputStream;
Deepanshu Gupta642cff52015-05-22 15:47:16 -070049import java.util.Iterator;
Adam Lesinski282e1812014-01-23 18:17:42 -080050
51/**
52 *
53 */
54public final class BridgeResources extends Resources {
55
56 private BridgeContext mContext;
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -070057 private LayoutlibCallback mLayoutlibCallback;
Adam Lesinski282e1812014-01-23 18:17:42 -080058 private boolean[] mPlatformResourceFlag = new boolean[1];
Alan Viveretteedc46642014-02-01 01:43:16 -080059 private TypedValue mTmpValue = new TypedValue();
Adam Lesinski282e1812014-01-23 18:17:42 -080060
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;
69 public NinePatchInputStream(File file) throws FileNotFoundException {
70 super(file);
71 }
72
73 @Override
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
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 */
98 public static Resources initSystem(BridgeContext context,
99 AssetManager assets,
100 DisplayMetrics metrics,
101 Configuration config,
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700102 LayoutlibCallback layoutlibCallback) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800103 return Resources.mSystem = new BridgeResources(context,
104 assets,
105 metrics,
106 config,
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700107 layoutlibCallback);
Adam Lesinski282e1812014-01-23 18:17:42 -0800108 }
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 */
114 public static void disposeSystem() {
115 if (Resources.mSystem instanceof BridgeResources) {
116 ((BridgeResources)(Resources.mSystem)).mContext = null;
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700117 ((BridgeResources)(Resources.mSystem)).mLayoutlibCallback = null;
Adam Lesinski282e1812014-01-23 18:17:42 -0800118 }
119 Resources.mSystem = null;
120 }
121
122 private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics,
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700123 Configuration config, LayoutlibCallback layoutlibCallback) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800124 super(assets, metrics, config);
125 mContext = context;
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700126 mLayoutlibCallback = layoutlibCallback;
Adam Lesinski282e1812014-01-23 18:17:42 -0800127 }
128
129 public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile) {
130 return new BridgeTypedArray(this, mContext, numEntries, platformFile);
131 }
132
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.
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700146 if (mLayoutlibCallback != null) {
147 resourceInfo = mLayoutlibCallback.resolveResourceId(id);
Adam Lesinski282e1812014-01-23 18:17:42 -0800148
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
Deepanshu Gupta8250a822014-02-18 20:21:24 -0800162 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) {
Deepanshu Gupta4a605c62014-10-28 10:50:00 -0700166 return ResourceHelper.getDrawable(value.getSecond(), mContext, theme);
Adam Lesinski282e1812014-01-23 18:17:42 -0800167 }
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
Deepanshu Gupta4d8e0782015-05-20 18:25:58 -0700177 public int getColor(int id, Theme theme) throws NotFoundException {
Adam Lesinski282e1812014-01-23 18:17:42 -0800178 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
179
180 if (value != null) {
Deepanshu Gupta68501b82015-02-23 11:52:10 -0800181 ResourceValue resourceValue = value.getSecond();
Adam Lesinski282e1812014-01-23 18:17:42 -0800182 try {
Deepanshu Gupta68501b82015-02-23 11:52:10 -0800183 return ResourceHelper.getColor(resourceValue.getValue());
Adam Lesinski282e1812014-01-23 18:17:42 -0800184 } catch (NumberFormatException e) {
Deepanshu Gupta68501b82015-02-23 11:52:10 -0800185 // 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);
Adam Lesinski282e1812014-01-23 18:17:42 -0800196 return 0;
197 }
198 }
199
Deepanshu Gupta4d8e0782015-05-20 18:25:58 -0700200 // 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();
Adam Lesinski282e1812014-01-23 18:17:42 -0800204 }
205
206 @Override
Deepanshu Gupta4d8e0782015-05-20 18:25:58 -0700207 public ColorStateList getColorStateList(int id, Theme theme) throws NotFoundException {
Adam Lesinski282e1812014-01-23 18:17:42 -0800208 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) {
Deepanshu Gupta4d8e0782015-05-20 18:25:58 -0700214 return stateList.obtainForTheme(theme);
Adam Lesinski282e1812014-01-23 18:17:42 -0800215 }
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
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
Deepanshu Gupta642cff52015-05-22 15:47:16 -0700249 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
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 */
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
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
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
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
Adam Lesinski282e1812014-01-23 18:17:42 -0800388 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) {
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700398 parser = mLayoutlibCallback.getParser(value);
Adam Lesinski282e1812014-01-23 18:17:42 -0800399 }
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
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
466 public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
467 return mContext.obtainStyledAttributes(set, attrs);
468 }
469
470 @Override
471 public TypedArray obtainTypedArray(int id) throws NotFoundException {
472 throw new UnsupportedOperationException();
473 }
474
475
476 @Override
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
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
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
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) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800577 try {
Deepanshu Gupta642cff52015-05-22 15:47:16 -0700578 return getInt(v);
Adam Lesinski282e1812014-01-23 18:17:42 -0800579 } 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
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
617 public String getResourceEntryName(int resid) throws NotFoundException {
618 throw new UnsupportedOperationException();
619 }
620
621 @Override
622 public String getResourceName(int resid) throws NotFoundException {
623 throw new UnsupportedOperationException();
624 }
625
626 @Override
627 public String getResourceTypeName(int resid) throws NotFoundException {
628 throw new UnsupportedOperationException();
629 }
630
631 @Override
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
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
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
687 public void getValue(String name, TypedValue outValue, boolean resolveRefs)
688 throws NotFoundException {
689 throw new UnsupportedOperationException();
690 }
691
692 @Override
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
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
Adam Lesinski282e1812014-01-23 18:17:42 -0800751 @Override
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
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
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 */
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.
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700827 if (resourceInfo == null && mLayoutlibCallback != null) {
828 resourceInfo = mLayoutlibCallback.resolveResourceId(id);
Adam Lesinski282e1812014-01-23 18:17:42 -0800829 }
830
Deepanshu Gupta642cff52015-05-22 15:47:16 -0700831 String message;
Adam Lesinski282e1812014-01-23 18:17:42 -0800832 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 }
Deepanshu Gupta642cff52015-05-22 15:47:16 -0700843
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 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800854}