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.SdkConstants;
     20 import com.android.ide.common.rendering.api.ArrayResourceValue;
     21 import com.android.ide.common.rendering.api.LayoutLog;
     22 import com.android.ide.common.rendering.api.LayoutlibCallback;
     23 import com.android.ide.common.rendering.api.ResourceValue;
     24 import com.android.layoutlib.bridge.Bridge;
     25 import com.android.layoutlib.bridge.BridgeConstants;
     26 import com.android.layoutlib.bridge.android.BridgeContext;
     27 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
     28 import com.android.layoutlib.bridge.impl.ParserFactory;
     29 import com.android.layoutlib.bridge.impl.ResourceHelper;
     30 import com.android.ninepatch.NinePatch;
     31 import com.android.resources.ResourceType;
     32 import com.android.util.Pair;
     33 
     34 import org.xmlpull.v1.XmlPullParser;
     35 import org.xmlpull.v1.XmlPullParserException;
     36 
     37 import android.annotation.NonNull;
     38 import android.annotation.Nullable;
     39 import android.graphics.drawable.Drawable;
     40 import android.util.AttributeSet;
     41 import android.util.DisplayMetrics;
     42 import android.util.TypedValue;
     43 import android.view.ViewGroup.LayoutParams;
     44 
     45 import java.io.File;
     46 import java.io.FileInputStream;
     47 import java.io.FileNotFoundException;
     48 import java.io.InputStream;
     49 import java.util.Iterator;
     50 
     51 /**
     52  *
     53  */
     54 public final class BridgeResources extends Resources {
     55 
     56     private BridgeContext mContext;
     57     private LayoutlibCallback mLayoutlibCallback;
     58     private boolean[] mPlatformResourceFlag = new boolean[1];
     59     private TypedValue mTmpValue = new TypedValue();
     60 
     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,
    102             LayoutlibCallback layoutlibCallback) {
    103         return Resources.mSystem = new BridgeResources(context,
    104                 assets,
    105                 metrics,
    106                 config,
    107                 layoutlibCallback);
    108     }
    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;
    117             ((BridgeResources)(Resources.mSystem)).mLayoutlibCallback = null;
    118         }
    119         Resources.mSystem = null;
    120     }
    121 
    122     private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics,
    123             Configuration config, LayoutlibCallback layoutlibCallback) {
    124         super(assets, metrics, config);
    125         mContext = context;
    126         mLayoutlibCallback = layoutlibCallback;
    127     }
    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.
    146         if (mLayoutlibCallback != null) {
    147             resourceInfo = mLayoutlibCallback.resolveResourceId(id);
    148 
    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
    162     public Drawable getDrawable(int id, Theme theme) {
    163         Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
    164 
    165         if (value != null) {
    166             return ResourceHelper.getDrawable(value.getSecond(), mContext, theme);
    167         }
    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
    177     public int getColor(int id, Theme theme) throws NotFoundException {
    178         Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
    179 
    180         if (value != null) {
    181             ResourceValue resourceValue = value.getSecond();
    182             try {
    183                 return ResourceHelper.getColor(resourceValue.getValue());
    184             } catch (NumberFormatException e) {
    185                 // 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);
    196                 return 0;
    197             }
    198         }
    199 
    200         // 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();
    204     }
    205 
    206     @Override
    207     public ColorStateList getColorStateList(int id, Theme theme) throws NotFoundException {
    208         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) {
    214                 return stateList.obtainForTheme(theme);
    215             }
    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
    249     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
    388     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) {
    398                     parser = mLayoutlibCallback.getParser(value);
    399                 }
    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) {
    577                     try {
    578                         return getInt(v);
    579                     } 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 
    751     @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.
    827         if (resourceInfo == null && mLayoutlibCallback != null) {
    828             resourceInfo = mLayoutlibCallback.resolveResourceId(id);
    829         }
    830 
    831         String message;
    832         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     }
    843 
    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     }
    854 }
    855