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