2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.layoutlib.bridge.android;
19 import com.android.ide.common.rendering.api.IProjectCallback;
20 import com.android.ide.common.rendering.api.LayoutLog;
21 import com.android.ide.common.rendering.api.ResourceValue;
22 import com.android.layoutlib.bridge.Bridge;
23 import com.android.layoutlib.bridge.BridgeConstants;
24 import com.android.layoutlib.bridge.impl.ResourceHelper;
25 import com.android.ninepatch.NinePatch;
26 import com.android.resources.ResourceType;
27 import com.android.util.Pair;
29 import org.kxml2.io.KXmlParser;
30 import org.xmlpull.v1.XmlPullParser;
31 import org.xmlpull.v1.XmlPullParserException;
33 import android.content.res.AssetFileDescriptor;
34 import android.content.res.AssetManager;
35 import android.content.res.ColorStateList;
36 import android.content.res.Configuration;
37 import android.content.res.Resources;
38 import android.content.res.TypedArray;
39 import android.content.res.XmlResourceParser;
40 import android.graphics.drawable.Drawable;
41 import android.util.AttributeSet;
42 import android.util.DisplayMetrics;
43 import android.util.TypedValue;
44 import android.view.ViewGroup.LayoutParams;
47 import java.io.FileInputStream;
48 import java.io.FileNotFoundException;
49 import java.io.InputStream;
54 public final class BridgeResources extends Resources {
56 private BridgeContext mContext;
57 private IProjectCallback mProjectCallback;
58 private boolean[] mPlatformResourceFlag = new boolean[1];
61 * Simpler wrapper around FileInputStream. This is used when the input stream represent
62 * not a normal bitmap but a nine patch.
63 * This is useful when the InputStream is created in a method but used in another that needs
64 * to know whether this is 9-patch or not, such as BitmapFactory.
66 public class NinePatchInputStream extends FileInputStream {
67 private boolean mFakeMarkSupport = true;
68 public NinePatchInputStream(File file) throws FileNotFoundException {
73 public boolean markSupported() {
74 if (mFakeMarkSupport) {
75 // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
79 return super.markSupported();
82 public void disableFakeMarkSupport() {
83 // disable fake mark support so that in case codec actually try to use them
84 // we don't lie to them.
85 mFakeMarkSupport = false;
90 * This initializes the static field {@link Resources#mSystem} which is used
91 * by methods who get global resources using {@link Resources#getSystem()}.
93 * They will end up using our bridge resources.
95 * {@link Bridge} calls this method after setting up a new bridge.
97 /*package*/ static Resources initSystem(BridgeContext context,
99 DisplayMetrics metrics,
100 Configuration config,
101 IProjectCallback projectCallback) {
102 return Resources.mSystem = new BridgeResources(context,
110 * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects
111 * around that would prevent us from unloading the library.
113 /*package*/ static void disposeSystem() {
114 if (Resources.mSystem instanceof BridgeResources) {
115 ((BridgeResources)(Resources.mSystem)).mContext = null;
116 ((BridgeResources)(Resources.mSystem)).mProjectCallback = null;
118 Resources.mSystem = null;
121 private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics,
122 Configuration config, IProjectCallback projectCallback) {
123 super(assets, metrics, config);
125 mProjectCallback = projectCallback;
128 public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile) {
129 return new BridgeTypedArray(this, mContext, numEntries, platformFile);
132 private ResourceValue getResourceValue(int id, boolean[] platformResFlag_out) {
133 // first get the String related to this id in the framework
134 Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
136 if (resourceInfo != null) {
137 platformResFlag_out[0] = true;
138 return mContext.getRenderResources().getFrameworkResource(
139 resourceInfo.getFirst(), resourceInfo.getSecond());
142 // didn't find a match in the framework? look in the project.
143 if (mProjectCallback != null) {
144 resourceInfo = mProjectCallback.resolveResourceId(id);
146 if (resourceInfo != null) {
147 platformResFlag_out[0] = false;
148 return mContext.getRenderResources().getProjectResource(
149 resourceInfo.getFirst(), resourceInfo.getSecond());
157 public Drawable getDrawable(int id) throws NotFoundException {
158 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
161 return ResourceHelper.getDrawable(value, mContext);
164 // id was not found or not resolved. Throw a NotFoundException.
167 // this is not used since the method above always throws
172 public int getColor(int id) throws NotFoundException {
173 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
177 return ResourceHelper.getColor(value.getValue());
178 } catch (NumberFormatException e) {
179 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e,
185 // id was not found or not resolved. Throw a NotFoundException.
188 // this is not used since the method above always throws
193 public ColorStateList getColorStateList(int id) throws NotFoundException {
194 ResourceValue resValue = getResourceValue(id, mPlatformResourceFlag);
196 if (resValue != null) {
197 ColorStateList stateList = ResourceHelper.getColorStateList(resValue, mContext);
198 if (stateList != null) {
203 // id was not found or not resolved. Throw a NotFoundException.
206 // this is not used since the method above always throws
211 public CharSequence getText(int id) throws NotFoundException {
212 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
215 return value.getValue();
218 // id was not found or not resolved. Throw a NotFoundException.
221 // this is not used since the method above always throws
226 public XmlResourceParser getLayout(int id) throws NotFoundException {
227 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
230 XmlPullParser parser = null;
233 // check if the current parser can provide us with a custom parser.
234 if (mPlatformResourceFlag[0] == false) {
235 parser = mProjectCallback.getParser(value.getName());
238 // create a new one manually if needed.
239 if (parser == null) {
240 File xml = new File(value.getValue());
242 // we need to create a pull parser around the layout XML file, and then
243 // give that to our XmlBlockParser
244 parser = new KXmlParser();
245 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
246 parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$);
250 if (parser != null) {
251 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
253 } catch (XmlPullParserException e) {
254 Bridge.getLog().error(LayoutLog.TAG_BROKEN,
255 "Failed to configure parser for " + value.getValue(), e, null /*data*/);
256 // we'll return null below.
257 } catch (FileNotFoundException e) {
258 // this shouldn't happen since we check above.
263 // id was not found or not resolved. Throw a NotFoundException.
266 // this is not used since the method above always throws
271 public XmlResourceParser getAnimation(int id) throws NotFoundException {
272 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
275 XmlPullParser parser = null;
278 File xml = new File(value.getValue());
280 // we need to create a pull parser around the layout XML file, and then
281 // give that to our XmlBlockParser
282 parser = new KXmlParser();
283 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
284 parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$);
286 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
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.
298 // id was not found or not resolved. Throw a NotFoundException.
301 // this is not used since the method above always throws
306 public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
307 return mContext.obtainStyledAttributes(set, attrs);
311 public TypedArray obtainTypedArray(int id) throws NotFoundException {
312 throw new UnsupportedOperationException();
317 public float getDimension(int id) throws NotFoundException {
318 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
321 String v = value.getValue();
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;
331 if (ResourceHelper.stringToFloat(v, mTmpValue) &&
332 mTmpValue.type == TypedValue.TYPE_DIMENSION) {
333 return mTmpValue.getDimension(mMetrics);
338 // id was not found or not resolved. Throw a NotFoundException.
341 // this is not used since the method above always throws
346 public int getDimensionPixelOffset(int id) throws NotFoundException {
347 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
350 String v = value.getValue();
353 if (ResourceHelper.stringToFloat(v, mTmpValue) &&
354 mTmpValue.type == TypedValue.TYPE_DIMENSION) {
355 return TypedValue.complexToDimensionPixelOffset(mTmpValue.data, mMetrics);
360 // id was not found or not resolved. Throw a NotFoundException.
363 // this is not used since the method above always throws
368 public int getDimensionPixelSize(int id) throws NotFoundException {
369 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
372 String v = value.getValue();
375 if (ResourceHelper.stringToFloat(v, mTmpValue) &&
376 mTmpValue.type == TypedValue.TYPE_DIMENSION) {
377 return TypedValue.complexToDimensionPixelSize(mTmpValue.data, mMetrics);
382 // id was not found or not resolved. Throw a NotFoundException.
385 // this is not used since the method above always throws
390 public int getInteger(int id) throws NotFoundException {
391 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
393 if (value != null && value.getValue() != null) {
394 String v = value.getValue();
396 if (v.startsWith("0x")) {
401 return Integer.parseInt(v, radix);
402 } catch (NumberFormatException e) {
403 // return exception below
407 // id was not found or not resolved. Throw a NotFoundException.
410 // this is not used since the method above always throws
415 public String getResourceEntryName(int resid) throws NotFoundException {
416 throw new UnsupportedOperationException();
420 public String getResourceName(int resid) throws NotFoundException {
421 throw new UnsupportedOperationException();
425 public String getResourceTypeName(int resid) throws NotFoundException {
426 throw new UnsupportedOperationException();
430 public String getString(int id, Object... formatArgs) throws NotFoundException {
431 String s = getString(id);
433 return String.format(s, formatArgs);
437 // id was not found or not resolved. Throw a NotFoundException.
440 // this is not used since the method above always throws
445 public String getString(int id) throws NotFoundException {
446 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
448 if (value != null && value.getValue() != null) {
449 return value.getValue();
452 // id was not found or not resolved. Throw a NotFoundException.
455 // this is not used since the method above always throws
460 public void getValue(int id, TypedValue outValue, boolean resolveRefs)
461 throws NotFoundException {
462 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
465 String v = value.getValue();
468 if (ResourceHelper.stringToFloat(v, outValue)) {
472 // else it's a string
473 outValue.type = TypedValue.TYPE_STRING;
479 // id was not found or not resolved. Throw a NotFoundException.
484 public void getValue(String name, TypedValue outValue, boolean resolveRefs)
485 throws NotFoundException {
486 throw new UnsupportedOperationException();
490 public XmlResourceParser getXml(int id) throws NotFoundException {
491 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
494 String v = value.getValue();
497 // check this is a file
498 File f = new File(value.getValue());
501 KXmlParser parser = new KXmlParser();
502 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
503 parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$);
505 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
506 } catch (XmlPullParserException e) {
507 NotFoundException newE = new NotFoundException();
510 } catch (FileNotFoundException e) {
511 NotFoundException newE = new NotFoundException();
519 // id was not found or not resolved. Throw a NotFoundException.
522 // this is not used since the method above always throws
527 public XmlResourceParser loadXmlResourceParser(String file, int id,
528 int assetCookie, String type) throws NotFoundException {
529 // even though we know the XML file to load directly, we still need to resolve the
530 // id so that we can know if it's a platform or project resource.
531 // (mPlatformResouceFlag will get the result and will be used later).
532 getResourceValue(id, mPlatformResourceFlag);
534 File f = new File(file);
536 KXmlParser parser = new KXmlParser();
537 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
538 parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$);
540 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
541 } catch (XmlPullParserException e) {
542 NotFoundException newE = new NotFoundException();
545 } catch (FileNotFoundException e) {
546 NotFoundException newE = new NotFoundException();
554 public InputStream openRawResource(int id) throws NotFoundException {
555 ResourceValue value = getResourceValue(id, mPlatformResourceFlag);
558 String path = value.getValue();
561 // check this is a file
562 File f = new File(path);
565 // if it's a nine-patch return a custom input stream so that
566 // other methods (mainly bitmap factory) can detect it's a 9-patch
567 // and actually load it as a 9-patch instead of a normal bitmap
568 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
569 return new NinePatchInputStream(f);
571 return new FileInputStream(f);
572 } catch (FileNotFoundException e) {
573 NotFoundException newE = new NotFoundException();
581 // id was not found or not resolved. Throw a NotFoundException.
584 // this is not used since the method above always throws
589 public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
590 getValue(id, value, true);
592 String path = value.string.toString();
594 File f = new File(path);
597 // if it's a nine-patch return a custom input stream so that
598 // other methods (mainly bitmap factory) can detect it's a 9-patch
599 // and actually load it as a 9-patch instead of a normal bitmap
600 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
601 return new NinePatchInputStream(f);
603 return new FileInputStream(f);
604 } catch (FileNotFoundException e) {
605 NotFoundException exception = new NotFoundException();
606 exception.initCause(e);
611 throw new NotFoundException();
615 public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
616 throw new UnsupportedOperationException();
620 * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type.
621 * @param id the id of the resource
622 * @throws NotFoundException
624 private void throwException(int id) throws NotFoundException {
625 // first get the String related to this id in the framework
626 Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
628 // if the name is unknown in the framework, get it from the custom view loader.
629 if (resourceInfo == null && mProjectCallback != null) {
630 resourceInfo = mProjectCallback.resolveResourceId(id);
633 String message = null;
634 if (resourceInfo != null) {
635 message = String.format(
636 "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
637 resourceInfo.getFirst(), id, resourceInfo.getSecond());
639 message = String.format(
640 "Could not resolve resource value: 0x%1$X.", id);
643 throw new NotFoundException(message);