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