Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
      4 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
      5 import static android.os.Build.VERSION_CODES.LOLLIPOP;
      6 import static android.os.Build.VERSION_CODES.M;
      7 import static android.os.Build.VERSION_CODES.N;
      8 import static android.os.Build.VERSION_CODES.N_MR1;
      9 import static android.os.Build.VERSION_CODES.O;
     10 import static android.os.Build.VERSION_CODES.O_MR1;
     11 import static android.os.Build.VERSION_CODES.P;
     12 import static android.os.Build.VERSION_CODES.Q;
     13 
     14 import static org.robolectric.RuntimeEnvironment.castNativePtr;
     15 import static org.robolectric.shadow.api.Shadow.directlyOn;
     16 import static org.robolectric.shadow.api.Shadow.invokeConstructor;
     17 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
     18 
     19 import android.annotation.SuppressLint;
     20 import android.content.res.ApkAssets;
     21 import android.content.res.AssetFileDescriptor;
     22 import android.content.res.AssetManager;
     23 import android.content.res.Resources;
     24 import android.content.res.TypedArray;
     25 import android.content.res.XmlResourceParser;
     26 import android.os.Build;
     27 import android.os.Build.VERSION_CODES;
     28 import android.os.ParcelFileDescriptor;
     29 import android.util.AttributeSet;
     30 import android.util.SparseArray;
     31 import android.util.TypedValue;
     32 import com.google.common.collect.Ordering;
     33 import dalvik.system.VMRuntime;
     34 import java.io.ByteArrayInputStream;
     35 import java.io.File;
     36 import java.io.FileInputStream;
     37 import java.io.FileNotFoundException;
     38 import java.io.FileOutputStream;
     39 import java.io.IOException;
     40 import java.io.InputStream;
     41 import java.net.MalformedURLException;
     42 import java.net.URL;
     43 import java.nio.file.Files;
     44 import java.util.ArrayList;
     45 import java.util.Arrays;
     46 import java.util.Collection;
     47 import java.util.Collections;
     48 import java.util.HashMap;
     49 import java.util.List;
     50 import java.util.Map;
     51 import java.util.Set;
     52 import java.util.concurrent.CopyOnWriteArraySet;
     53 import java.util.zip.ZipEntry;
     54 import java.util.zip.ZipInputStream;
     55 import javax.annotation.Nonnull;
     56 import org.robolectric.RuntimeEnvironment;
     57 import org.robolectric.android.XmlResourceParserImpl;
     58 import org.robolectric.annotation.HiddenApi;
     59 import org.robolectric.annotation.Implementation;
     60 import org.robolectric.annotation.Implements;
     61 import org.robolectric.annotation.RealObject;
     62 import org.robolectric.annotation.Resetter;
     63 import org.robolectric.res.AttrData;
     64 import org.robolectric.res.AttributeResource;
     65 import org.robolectric.res.EmptyStyle;
     66 import org.robolectric.res.FileTypedResource;
     67 import org.robolectric.res.Fs;
     68 import org.robolectric.res.FsFile;
     69 import org.robolectric.res.ResName;
     70 import org.robolectric.res.ResType;
     71 import org.robolectric.res.ResourceIds;
     72 import org.robolectric.res.ResourceTable;
     73 import org.robolectric.res.Style;
     74 import org.robolectric.res.StyleData;
     75 import org.robolectric.res.StyleResolver;
     76 import org.robolectric.res.ThemeStyleSet;
     77 import org.robolectric.res.TypedResource;
     78 import org.robolectric.res.android.Asset;
     79 import org.robolectric.res.android.Registries;
     80 import org.robolectric.res.android.ResTable_config;
     81 import org.robolectric.res.builder.XmlBlock;
     82 import org.robolectric.shadow.api.Shadow;
     83 import org.robolectric.shadows.ShadowAssetManager.Picker;
     84 import org.robolectric.util.Logger;
     85 import org.robolectric.util.ReflectionHelpers;
     86 
     87 @SuppressLint("NewApi")
     88 @Implements(value = AssetManager.class, /* this one works for P too... maxSdk = VERSION_CODES.O_MR1,*/
     89     looseSignatures = true, shadowPicker = Picker.class)
     90 public class ShadowLegacyAssetManager extends ShadowAssetManager {
     91 
     92   public static final Ordering<String> ATTRIBUTE_TYPE_PRECIDENCE =
     93       Ordering.explicit(
     94           "reference",
     95           "color",
     96           "boolean",
     97           "integer",
     98           "fraction",
     99           "dimension",
    100           "float",
    101           "enum",
    102           "flag",
    103           "flags",
    104           "string");
    105 
    106   static boolean strictErrors = false;
    107 
    108   private static final int STYLE_NUM_ENTRIES = 6;
    109   private static final int STYLE_TYPE = 0;
    110   private static final int STYLE_DATA = 1;
    111   private static final int STYLE_ASSET_COOKIE = 2;
    112   private static final int STYLE_RESOURCE_ID = 3;
    113   private static final int STYLE_CHANGING_CONFIGURATIONS = 4;
    114   private static final int STYLE_DENSITY = 5;
    115 
    116   private static long nextInternalThemeId = 1000;
    117   private static final Map<Long, NativeTheme> nativeThemes = new HashMap<>();
    118 
    119   @RealObject
    120   protected AssetManager realObject;
    121 
    122   private ResourceTable resourceTable;
    123 
    124   class NativeTheme {
    125     private ThemeStyleSet themeStyleSet;
    126 
    127     public NativeTheme(ThemeStyleSet themeStyleSet) {
    128       this.themeStyleSet = themeStyleSet;
    129     }
    130 
    131     public ShadowLegacyAssetManager getShadowAssetManager() {
    132       return ShadowLegacyAssetManager.this;
    133     }
    134   }
    135 
    136   ResTable_config config = new ResTable_config();
    137   private Set<FsFile> assetDirs = new CopyOnWriteArraySet<>();
    138 
    139   private void convertAndFill(AttributeResource attribute, TypedValue outValue, ResTable_config config, boolean resolveRefs) {
    140     if (attribute.isNull()) {
    141       outValue.type = TypedValue.TYPE_NULL;
    142       outValue.data = TypedValue.DATA_NULL_UNDEFINED;
    143       return;
    144     } else if (attribute.isEmpty()) {
    145       outValue.type = TypedValue.TYPE_NULL;
    146       outValue.data = TypedValue.DATA_NULL_EMPTY;
    147       return;
    148     }
    149 
    150     // short-circuit Android caching of loaded resources cuz our string positions don't remain stable...
    151     outValue.assetCookie = Converter.getNextStringCookie();
    152     outValue.changingConfigurations = 0;
    153 
    154     // TODO: Handle resource and style references
    155     if (attribute.isStyleReference()) {
    156       return;
    157     }
    158 
    159     while (attribute.isResourceReference()) {
    160       Integer resourceId;
    161       ResName resName = attribute.getResourceReference();
    162       if (attribute.getReferenceResId() != null) {
    163         resourceId = attribute.getReferenceResId();
    164       } else {
    165         resourceId = resourceTable.getResourceId(resName);
    166       }
    167 
    168       if (resourceId == null) {
    169         throw new Resources.NotFoundException("unknown resource " + resName);
    170       }
    171       outValue.type = TypedValue.TYPE_REFERENCE;
    172       if (!resolveRefs) {
    173         // Just return the resourceId if resolveRefs is false.
    174         outValue.data = resourceId;
    175         return;
    176       }
    177 
    178       outValue.resourceId = resourceId;
    179 
    180       TypedResource dereferencedRef = resourceTable.getValue(resName, config);
    181       if (dereferencedRef == null) {
    182         Logger.strict("couldn't resolve %s from %s", resName.getFullyQualifiedName(), attribute);
    183         return;
    184       } else {
    185         if (dereferencedRef.isFile()) {
    186           outValue.type = TypedValue.TYPE_STRING;
    187           outValue.data = 0;
    188           outValue.assetCookie = Converter.getNextStringCookie();
    189           outValue.string = dereferencedRef.asString();
    190           return;
    191         } else if (dereferencedRef.getData() instanceof String) {
    192           attribute = new AttributeResource(attribute.resName, dereferencedRef.asString(), resName.packageName);
    193           if (attribute.isResourceReference()) {
    194             continue;
    195           }
    196           if (resolveRefs) {
    197             Converter.getConverter(dereferencedRef.getResType()).fillTypedValue(attribute.value, outValue);
    198             return;
    199           }
    200         }
    201       }
    202       break;
    203     }
    204 
    205     if (attribute.isNull()) {
    206       outValue.type = TypedValue.TYPE_NULL;
    207       return;
    208     }
    209 
    210     TypedResource attrTypeData = getAttrTypeData(attribute.resName);
    211     if (attrTypeData != null) {
    212       AttrData attrData = (AttrData) attrTypeData.getData();
    213       String format = attrData.getFormat();
    214       String[] types = format.split("\\|");
    215       Arrays.sort(types, ATTRIBUTE_TYPE_PRECIDENCE);
    216       for (String type : types) {
    217         if ("reference".equals(type)) continue; // already handled above
    218         Converter converter = Converter.getConverterFor(attrData, type);
    219 
    220         if (converter != null) {
    221           if (converter.fillTypedValue(attribute.value, outValue)) {
    222             return;
    223           }
    224         }
    225       }
    226     } else {
    227       /**
    228        * In cases where the runtime framework doesn't know this attribute, e.g: viewportHeight (added in 21) on a
    229        * KitKat runtine, then infer the attribute type from the value.
    230        *
    231        * TODO: When we are able to pass the SDK resources from the build environment then we can remove this
    232        * and replace the NullResourceLoader with simple ResourceProvider that only parses attribute type information.
    233        */
    234       ResType resType = ResType.inferFromValue(attribute.value);
    235       Converter.getConverter(resType).fillTypedValue(attribute.value, outValue);
    236     }
    237   }
    238 
    239 
    240   public TypedResource getAttrTypeData(ResName resName) {
    241     return resourceTable.getValue(resName, config);
    242   }
    243 
    244   @Implementation
    245   protected void __constructor__() {
    246     resourceTable = RuntimeEnvironment.getAppResourceTable();
    247 
    248 
    249     if (RuntimeEnvironment.getApiLevel() >= P) {
    250       invokeConstructor(AssetManager.class, realObject);
    251     }
    252 
    253   }
    254 
    255   @Implementation
    256   protected void __constructor__(boolean isSystem) {
    257     resourceTable = isSystem ? RuntimeEnvironment.getSystemResourceTable() : RuntimeEnvironment.getAppResourceTable();
    258 
    259 
    260     if (RuntimeEnvironment.getApiLevel() >= P) {
    261       invokeConstructor(AssetManager.class, realObject, from(boolean.class, isSystem));
    262     }
    263 
    264   }
    265 
    266   @Implementation(minSdk = P)
    267   protected static long nativeCreate() {
    268     // Return a fake pointer, must not be 0.
    269     return 1;
    270   }
    271 
    272   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    273   protected void init() {
    274     // no op
    275   }
    276 
    277   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    278   protected void init(boolean isSystem) {
    279     // no op
    280   }
    281 
    282   protected ResourceTable getResourceTable() {
    283     return resourceTable;
    284   }
    285 
    286   @HiddenApi @Implementation
    287   public CharSequence getResourceText(int ident) {
    288     TypedResource value = getAndResolve(ident, config, true);
    289     if (value == null) return null;
    290     return (CharSequence) value.getData();
    291   }
    292 
    293   @HiddenApi @Implementation
    294   public CharSequence getResourceBagText(int ident, int bagEntryId) {
    295     throw new UnsupportedOperationException(); // todo
    296   }
    297 
    298   @HiddenApi @Implementation(maxSdk = O_MR1)
    299   protected int getStringBlockCount() {
    300     return 0;
    301   }
    302 
    303   @HiddenApi @Implementation
    304   public String[] getResourceStringArray(final int id) {
    305     CharSequence[] resourceTextArray = getResourceTextArray(id);
    306     if (resourceTextArray == null) return null;
    307     String[] strings = new String[resourceTextArray.length];
    308     for (int i = 0; i < strings.length; i++) {
    309       strings[i] = resourceTextArray[i].toString();
    310     }
    311     return strings;
    312   }
    313 
    314   @HiddenApi @Implementation
    315   public int getResourceIdentifier(String name, String defType, String defPackage) {
    316     Integer resourceId = resourceTable.getResourceId(ResName.qualifyResName(name, defPackage, defType));
    317     return resourceId == null ? 0 : resourceId;
    318   }
    319 
    320   @HiddenApi @Implementation
    321   public boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) {
    322     TypedResource value = getAndResolve(ident, config, resolveRefs);
    323     if (value == null) return false;
    324 
    325     getConverter(value).fillTypedValue(value.getData(), outValue);
    326     return true;
    327   }
    328 
    329   private Converter getConverter(TypedResource value) {
    330     if (value instanceof FileTypedResource.Image
    331         || (value instanceof FileTypedResource
    332         && ((FileTypedResource) value).getFsFile().getName().endsWith(".xml"))) {
    333       return new Converter.FromFilePath();
    334     }
    335     return Converter.getConverter(value.getResType());
    336   }
    337 
    338   @HiddenApi @Implementation
    339   public CharSequence[] getResourceTextArray(int resId) {
    340     TypedResource value = getAndResolve(resId, config, true);
    341     if (value == null) return null;
    342     List<TypedResource> items = getConverter(value).getItems(value);
    343     CharSequence[] charSequences = new CharSequence[items.size()];
    344     for (int i = 0; i < items.size(); i++) {
    345       TypedResource typedResource = resolve(items.get(i), config, resId);
    346       charSequences[i] = getConverter(typedResource).asCharSequence(typedResource);
    347     }
    348     return charSequences;
    349   }
    350 
    351   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    352   public boolean getThemeValue(int themePtr, int ident, TypedValue outValue, boolean resolveRefs) {
    353     return getThemeValue((long) themePtr, ident, outValue, resolveRefs);
    354   }
    355 
    356   @HiddenApi @Implementation(minSdk = LOLLIPOP)
    357   public boolean getThemeValue(long themePtr, int ident, TypedValue outValue, boolean resolveRefs) {
    358     ResName resName = resourceTable.getResName(ident);
    359 
    360     ThemeStyleSet themeStyleSet = getNativeTheme(themePtr).themeStyleSet;
    361     AttributeResource attrValue = themeStyleSet.getAttrValue(resName);
    362     while(attrValue != null && attrValue.isStyleReference()) {
    363       ResName attrResName = attrValue.getStyleReference();
    364       if (attrValue.resName.equals(attrResName)) {
    365         Logger.info("huh... circular reference for %s?", attrResName.getFullyQualifiedName());
    366         return false;
    367       }
    368       attrValue = themeStyleSet.getAttrValue(attrResName);
    369     }
    370     if (attrValue != null) {
    371       convertAndFill(attrValue, outValue, config, resolveRefs);
    372       return true;
    373     }
    374     return false;
    375   }
    376 
    377   @HiddenApi @Implementation(maxSdk = O_MR1)
    378   protected Object ensureStringBlocks() {
    379     return null;
    380   }
    381 
    382   @Implementation
    383   protected final InputStream open(String fileName) throws IOException {
    384     return findAssetFile(fileName).getInputStream();
    385   }
    386 
    387   @Implementation
    388   protected final InputStream open(String fileName, int accessMode) throws IOException {
    389     return findAssetFile(fileName).getInputStream();
    390   }
    391 
    392   @Implementation
    393   protected final AssetFileDescriptor openFd(String fileName) throws IOException {
    394     File file = new File(findAssetFile(fileName).getPath());
    395     if (file.getPath().startsWith("jar")) {
    396       file = getFileFromZip(file);
    397     }
    398     ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    399     return new AssetFileDescriptor(parcelFileDescriptor, 0, file.length());
    400   }
    401 
    402   private FsFile findAssetFile(String fileName) throws IOException {
    403     for (FsFile assetDir : getAllAssetDirs()) {
    404       FsFile assetFile = assetDir.join(fileName);
    405       if (assetFile.exists()) {
    406         return assetFile;
    407       }
    408     }
    409 
    410     throw new FileNotFoundException("Asset file " + fileName + " not found");
    411   }
    412 
    413   /**
    414    * Extract an asset from a zipped up assets provided by the build system, this is required because there is no
    415    * way to get a FileDescriptor from a zip entry. This is a temporary measure for Bazel which can be removed
    416    * once binary resources are supported.
    417    */
    418   private static File getFileFromZip(File file) {
    419     File fileFromZip = null;
    420     String pathString = file.getPath();
    421     String zipFile = pathString.substring(pathString.lastIndexOf(":") + 1, pathString.indexOf("!"));
    422     String filePathInsideZip = pathString.split("!", 0)[1].substring(1);
    423     byte[] buffer = new byte[1024];
    424     try {
    425       File outputDir = Files.createTempDirectory("robolectric_assets").toFile();
    426       ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
    427       ZipEntry ze = zis.getNextEntry();
    428       while (ze != null) {
    429         String currentFilename = ze.getName();
    430         if (!currentFilename.equals(filePathInsideZip)) {
    431           ze = zis.getNextEntry();
    432           continue;
    433         }
    434         fileFromZip = new File(outputDir + File.separator + currentFilename);
    435         new File(fileFromZip.getParent()).mkdirs();
    436         FileOutputStream fos = new FileOutputStream(fileFromZip);
    437         int len;
    438         while ((len = zis.read(buffer)) > 0) {
    439           fos.write(buffer, 0, len);
    440         }
    441         fos.close();
    442         break;
    443       }
    444       zis.closeEntry();
    445       zis.close();
    446     } catch (IOException e) {
    447       throw new RuntimeException(e);
    448     }
    449     return fileFromZip;
    450   }
    451 
    452   @Implementation
    453   protected final String[] list(String path) throws IOException {
    454     List<String> assetFiles = new ArrayList<>();
    455 
    456     for (FsFile assetsDir : getAllAssetDirs()) {
    457       FsFile file;
    458       if (path.isEmpty()) {
    459         file = assetsDir;
    460       } else {
    461         file = assetsDir.join(path);
    462       }
    463 
    464       if (file.isDirectory()) {
    465         Collections.addAll(assetFiles, file.listFileNames());
    466       }
    467     }
    468     return assetFiles.toArray(new String[assetFiles.size()]);
    469   }
    470 
    471   @HiddenApi @Implementation(maxSdk = O_MR1)
    472   protected Number openAsset(String fileName, int mode) throws FileNotFoundException {
    473     return 0;
    474   }
    475 
    476   @HiddenApi @Implementation(maxSdk = O_MR1)
    477   protected ParcelFileDescriptor openAssetFd(String fileName, long[] outOffsets) throws IOException {
    478     return null;
    479   }
    480 
    481   @HiddenApi @Implementation
    482   public final InputStream openNonAsset(int cookie, String fileName, int accessMode) throws IOException {
    483     final ResName resName = qualifyFromNonAssetFileName(fileName);
    484 
    485     final FileTypedResource typedResource =
    486         (FileTypedResource) resourceTable.getValue(resName, config);
    487 
    488     if (typedResource == null) {
    489       throw new IOException("Unable to find resource for " + fileName);
    490     }
    491 
    492     InputStream stream;
    493     if (accessMode == AssetManager.ACCESS_STREAMING) {
    494       stream = typedResource.getFsFile().getInputStream();
    495     } else {
    496       stream = new ByteArrayInputStream(typedResource.getFsFile().getBytes());
    497     }
    498 
    499     if (RuntimeEnvironment.getApiLevel() >= P) {
    500       Asset asset = Asset.newFileAsset(typedResource);
    501       long assetPtr = Registries.NATIVE_ASSET_REGISTRY.register(asset);
    502       // Camouflage the InputStream as an AssetInputStream so subsequent instanceof checks pass.
    503       stream = ShadowAssetInputStream.createAssetInputStream(stream, assetPtr, realObject);
    504     }
    505 
    506     return stream;
    507   }
    508 
    509   @HiddenApi @Implementation(maxSdk = O_MR1)
    510   protected Number openNonAssetNative(int cookie, String fileName, int accessMode)
    511       throws FileNotFoundException {
    512     throw new IllegalStateException();
    513   }
    514 
    515   private ResName qualifyFromNonAssetFileName(String fileName) {
    516     // Resources from a jar belong to the "android" namespace, except when they come from "resource_files.zip"
    517     // when they are application resources produced by Bazel.
    518     if (fileName.startsWith("jar:") && !fileName.contains("resource_files.zip")) {
    519       // Must remove "jar:" prefix, or else qualifyFromFilePath fails on Windows
    520       return ResName.qualifyFromFilePath("android", fileName.replaceFirst("jar:", ""));
    521     } else {
    522       return ResName.qualifyFromFilePath(RuntimeEnvironment.application.getPackageName(), fileName);
    523     }
    524   }
    525 
    526   @HiddenApi @Implementation
    527   public final AssetFileDescriptor openNonAssetFd(int cookie, String fileName) throws IOException {
    528     throw new IllegalStateException();
    529   }
    530 
    531   @HiddenApi @Implementation(maxSdk = O_MR1)
    532   protected ParcelFileDescriptor openNonAssetFdNative(int cookie, String fileName, long[] outOffsets)
    533       throws IOException {
    534     throw new IllegalStateException();
    535   }
    536 
    537   @HiddenApi @Implementation(maxSdk = O_MR1)
    538   protected Number openXmlAssetNative(int cookie, String fileName) throws FileNotFoundException {
    539     throw new IllegalStateException();
    540   }
    541 
    542   @Implementation
    543   protected final XmlResourceParser openXmlResourceParser(int cookie, String fileName)
    544       throws IOException {
    545     XmlBlock xmlBlock = XmlBlock.create(Fs.fileFromPath(fileName), resourceTable.getPackageName());
    546     if (xmlBlock == null) {
    547       throw new Resources.NotFoundException(fileName);
    548     }
    549     return getXmlResourceParser(resourceTable, xmlBlock, resourceTable.getPackageName());
    550   }
    551 
    552   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    553   protected final long seekAsset(int asset, long offset, int whence) {
    554     return seekAsset((long) asset, offset, whence);
    555   }
    556 
    557   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    558   protected long seekAsset(long asset, long offset, int whence) {
    559     return 0;
    560   }
    561 
    562   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    563   protected final long getAssetLength(int asset) {
    564     return getAssetLength((long) asset);
    565   }
    566 
    567   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    568   protected long getAssetLength(long asset) {
    569     return 0;
    570   }
    571 
    572   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    573   protected final long getAssetRemainingLength(int asset) {
    574     return getAssetRemainingLength((long) asset);
    575   }
    576 
    577   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    578   protected long getAssetRemainingLength(long assetHandle) {
    579     return 0;
    580   }
    581 
    582   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    583   protected final void destroyAsset(int asset) {
    584     destroyAsset((long) asset);
    585   }
    586 
    587   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    588   protected void destroyAsset(long asset) {
    589     // no op
    590   }
    591 
    592   protected XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
    593     ResName resName = getResName(resId);
    594     ResName resolvedResName = resolveResName(resName, config);
    595     if (resolvedResName == null) {
    596       throw new RuntimeException("couldn't resolve " + resName.getFullyQualifiedName());
    597     }
    598     resName = resolvedResName;
    599 
    600     XmlBlock block = resourceTable.getXml(resName, config);
    601     if (block == null) {
    602       throw new Resources.NotFoundException(resName.getFullyQualifiedName());
    603     }
    604 
    605     ResourceTable resourceProvider = ResourceIds.isFrameworkResource(resId) ? RuntimeEnvironment.getSystemResourceTable() : RuntimeEnvironment.getCompileTimeResourceTable();
    606 
    607     return getXmlResourceParser(resourceProvider, block, resName.packageName);
    608   }
    609 
    610   private XmlResourceParser getXmlResourceParser(ResourceTable resourceProvider, XmlBlock block, String packageName) {
    611     return new XmlResourceParserImpl(block.getDocument(), block.getFilename(), block.getPackageName(),
    612         packageName, resourceProvider);
    613   }
    614 
    615   @HiddenApi @Implementation
    616   public int addAssetPath(String path) {
    617     assetDirs.add(getFsFileFromPath(path));
    618     return 1;
    619   }
    620 
    621   @HiddenApi @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M)
    622   final protected int addAssetPathNative(String path) {
    623     return addAssetPathNative(path, false);
    624   }
    625 
    626   @HiddenApi @Implementation(minSdk = N, maxSdk = O_MR1)
    627   protected int addAssetPathNative(String path, boolean appAsLib) {
    628     return 0;
    629   }
    630 
    631   @HiddenApi @Implementation(minSdk = P)
    632   public void setApkAssets(Object apkAssetsObject, Object invalidateCachesObject) {
    633     ApkAssets[] apkAssets = (ApkAssets[]) apkAssetsObject;
    634     boolean invalidateCaches = (boolean) invalidateCachesObject;
    635 
    636     for (ApkAssets apkAsset : apkAssets) {
    637       assetDirs.add(getFsFileFromPath(apkAsset.getAssetPath()));
    638     }
    639     directlyOn(realObject, AssetManager.class).setApkAssets(apkAssets, invalidateCaches);
    640   }
    641 
    642   private FsFile getFsFileFromPath(String property) {
    643     if (property.startsWith("jar")) {
    644       try {
    645         URL url = new URL(property);
    646         return Fs.fromURL(url);
    647       } catch (MalformedURLException e) {
    648         throw new RuntimeException(e);
    649       }
    650     } else {
    651       return Fs.fileFromPath(property);
    652     }
    653   }
    654 
    655   @HiddenApi @Implementation
    656   public boolean isUpToDate() {
    657     return true;
    658   }
    659 
    660   @HiddenApi @Implementation(maxSdk = M)
    661   public void setLocale(String locale) {
    662   }
    663 
    664   @Implementation
    665   protected String[] getLocales() {
    666     return new String[0]; // todo
    667   }
    668 
    669   @HiddenApi @Implementation(maxSdk = N_MR1)
    670   final public void setConfiguration(int mcc, int mnc, String locale,
    671       int orientation, int touchscreen, int density, int keyboard,
    672       int keyboardHidden, int navigation, int screenWidth, int screenHeight,
    673       int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
    674       int screenLayout, int uiMode, int sdkVersion) {
    675     setConfiguration(mcc, mnc, locale,
    676         orientation, touchscreen, density, keyboard,
    677         keyboardHidden, navigation, screenWidth, screenHeight,
    678         smallestScreenWidthDp, screenWidthDp, screenHeightDp,
    679         screenLayout, uiMode, 0, sdkVersion);
    680   }
    681 
    682   @HiddenApi @Implementation(minSdk = VERSION_CODES.O)
    683   public void setConfiguration(int mcc, int mnc, String locale,
    684       int orientation, int touchscreen, int density, int keyboard,
    685       int keyboardHidden, int navigation, int screenWidth, int screenHeight,
    686       int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
    687       int screenLayout, int uiMode, int colorMode, int majorVersion) {
    688     // AssetManager* am = assetManagerForJavaObject(env, clazz);
    689 
    690     ResTable_config config = new ResTable_config();
    691 
    692     // Constants duplicated from Java class android.content.res.Configuration.
    693     final int kScreenLayoutRoundMask = 0x300;
    694     final int kScreenLayoutRoundShift = 8;
    695 
    696     config.mcc = mcc;
    697     config.mnc = mnc;
    698     config.orientation = orientation;
    699     config.touchscreen = touchscreen;
    700     config.density = density;
    701     config.keyboard = keyboard;
    702     config.inputFlags = keyboardHidden;
    703     config.navigation = navigation;
    704     config.screenWidth = screenWidth;
    705     config.screenHeight = screenHeight;
    706     config.smallestScreenWidthDp = smallestScreenWidthDp;
    707     config.screenWidthDp = screenWidthDp;
    708     config.screenHeightDp = screenHeightDp;
    709     config.screenLayout = screenLayout;
    710     config.uiMode = uiMode;
    711     // config.colorMode = colorMode; // todo
    712     config.sdkVersion = majorVersion;
    713     config.minorVersion = 0;
    714 
    715     // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
    716     // in C++. We must extract the round qualifier out of the Java screenLayout and put it
    717     // into screenLayout2.
    718     config.screenLayout2 =
    719         (byte)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
    720 
    721     if (locale != null) {
    722       config.setBcp47Locale(locale);
    723     }
    724     // am->setConfiguration(config, locale8);
    725 
    726     this.config = config;
    727   }
    728 
    729   @HiddenApi @Implementation(maxSdk = O_MR1)
    730   public int[] getArrayIntResource(int resId) {
    731     TypedResource value = getAndResolve(resId, config, true);
    732     if (value == null) return null;
    733     List<TypedResource> items = getConverter(value).getItems(value);
    734     int[] ints = new int[items.size()];
    735     for (int i = 0; i < items.size(); i++) {
    736       TypedResource typedResource = resolve(items.get(i), config, resId);
    737       ints[i] = getConverter(typedResource).asInt(typedResource);
    738     }
    739     return ints;
    740   }
    741 
    742   @HiddenApi @Implementation(minSdk = P)
    743   protected int[] getResourceIntArray(int resId) {
    744     return getArrayIntResource(resId);
    745   }
    746 
    747   @HiddenApi @Implementation(maxSdk = O_MR1)
    748   protected String[] getArrayStringResource(int arrayResId) {
    749     return new String[0];
    750   }
    751 
    752   @HiddenApi @Implementation(maxSdk = O_MR1)
    753   protected int[] getArrayStringInfo(int arrayResId) {
    754     return new int[0];
    755   }
    756 
    757   @HiddenApi @Implementation(maxSdk = O_MR1)
    758   protected Number newTheme() {
    759     return null;
    760   }
    761 
    762   protected TypedArray getTypedArrayResource(Resources resources, int resId) {
    763     TypedResource value = getAndResolve(resId, config, true);
    764     if (value == null) {
    765       return null;
    766     }
    767     List<TypedResource> items = getConverter(value).getItems(value);
    768     return getTypedArray(resources, items, resId);
    769   }
    770 
    771   private TypedArray getTypedArray(Resources resources, List<TypedResource> typedResources, int resId) {
    772     final CharSequence[] stringData = new CharSequence[typedResources.size()];
    773     final int totalLen = typedResources.size() * STYLE_NUM_ENTRIES;
    774     final int[] data = new int[totalLen];
    775 
    776     for (int i = 0; i < typedResources.size(); i++) {
    777       final int offset = i * STYLE_NUM_ENTRIES;
    778       TypedResource typedResource = typedResources.get(i);
    779 
    780       // Classify the item.
    781       int type = getResourceType(typedResource);
    782       if (type == -1) {
    783         // This type is unsupported; leave empty.
    784         continue;
    785       }
    786 
    787       final TypedValue typedValue = new TypedValue();
    788 
    789       if (type == TypedValue.TYPE_REFERENCE) {
    790         final String reference = typedResource.asString();
    791         ResName refResName = AttributeResource.getResourceReference(reference,
    792             typedResource.getXmlContext().getPackageName(), null);
    793         typedValue.resourceId = resourceTable.getResourceId(refResName);
    794         typedValue.data = typedValue.resourceId;
    795         typedResource = resolve(typedResource, config, typedValue.resourceId);
    796 
    797         if (typedResource != null) {
    798           // Reclassify to a non-reference type.
    799           type = getResourceType(typedResource);
    800           if (type == TypedValue.TYPE_ATTRIBUTE) {
    801             type = TypedValue.TYPE_REFERENCE;
    802           } else if (type == -1) {
    803             // This type is unsupported; leave empty.
    804             continue;
    805           }
    806         }
    807       }
    808 
    809       if (type == TypedValue.TYPE_ATTRIBUTE) {
    810         final String reference = typedResource.asString();
    811         final ResName attrResName = AttributeResource.getStyleReference(reference,
    812             typedResource.getXmlContext().getPackageName(), "attr");
    813         typedValue.data = resourceTable.getResourceId(attrResName);
    814       }
    815 
    816       if (typedResource != null && type != TypedValue.TYPE_NULL && type != TypedValue.TYPE_ATTRIBUTE) {
    817         getConverter(typedResource).fillTypedValue(typedResource.getData(), typedValue);
    818       }
    819 
    820       data[offset + STYLE_TYPE] = type;
    821       data[offset + STYLE_RESOURCE_ID] = typedValue.resourceId;
    822       data[offset + STYLE_DATA] = typedValue.data;
    823       data[offset + STYLE_ASSET_COOKIE] = typedValue.assetCookie;
    824       data[offset + STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations;
    825       data[offset + STYLE_DENSITY] = typedValue.density;
    826       stringData[i] = typedResource == null ? null : typedResource.asString();
    827     }
    828 
    829     int[] indices = new int[typedResources.size() + 1]; /* keep zeroed out */
    830     return ShadowTypedArray.create(resources, null, data, indices, typedResources.size(), stringData);
    831   }
    832 
    833   private int getResourceType(TypedResource typedResource) {
    834     if (typedResource == null) {
    835       return -1;
    836     }
    837     final ResType resType = typedResource.getResType();
    838     int type;
    839     if (typedResource.getData() == null || resType == ResType.NULL) {
    840       type = TypedValue.TYPE_NULL;
    841     } else if (typedResource.isReference()) {
    842       type = TypedValue.TYPE_REFERENCE;
    843     } else if (resType == ResType.STYLE) {
    844       type = TypedValue.TYPE_ATTRIBUTE;
    845     } else if (resType == ResType.CHAR_SEQUENCE || resType == ResType.DRAWABLE) {
    846       type = TypedValue.TYPE_STRING;
    847     } else if (resType == ResType.INTEGER) {
    848       type = TypedValue.TYPE_INT_DEC;
    849     } else if (resType == ResType.FLOAT || resType == ResType.FRACTION) {
    850       type = TypedValue.TYPE_FLOAT;
    851     } else if (resType == ResType.BOOLEAN) {
    852       type = TypedValue.TYPE_INT_BOOLEAN;
    853     } else if (resType == ResType.DIMEN) {
    854       type = TypedValue.TYPE_DIMENSION;
    855     } else if (resType == ResType.COLOR) {
    856       type = TypedValue.TYPE_INT_COLOR_ARGB8;
    857     } else if (resType == ResType.TYPED_ARRAY || resType == ResType.CHAR_SEQUENCE_ARRAY) {
    858       type = TypedValue.TYPE_REFERENCE;
    859     } else {
    860       type = -1;
    861     }
    862     return type;
    863   }
    864 
    865   @HiddenApi @Implementation
    866   public Number createTheme() {
    867     synchronized (nativeThemes) {
    868       long nativePtr = nextInternalThemeId++;
    869       nativeThemes.put(nativePtr, new NativeTheme(new ThemeStyleSet()));
    870       return castNativePtr(nativePtr);
    871     }
    872   }
    873 
    874   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    875   protected static void dumpTheme(long theme, int priority, String tag, String prefix) {
    876     throw new UnsupportedOperationException("not yet implemented");
    877   }
    878 
    879   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    880   public void releaseTheme(int themePtr) {
    881     // no op
    882   }
    883 
    884   private static NativeTheme getNativeTheme(long themePtr) {
    885     NativeTheme nativeTheme;
    886     synchronized (nativeThemes) {
    887       nativeTheme = nativeThemes.get(themePtr);
    888     }
    889     if (nativeTheme == null) {
    890       throw new RuntimeException("no theme " + themePtr + " found in AssetManager");
    891     }
    892     return nativeTheme;
    893   }
    894 
    895   @HiddenApi @Implementation(minSdk = LOLLIPOP)
    896   public void releaseTheme(long themePtr) {
    897     synchronized (nativeThemes) {
    898       nativeThemes.remove(themePtr);
    899     }
    900   }
    901 
    902   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    903   protected void deleteTheme(int theme) {
    904     deleteTheme((long) theme);
    905   }
    906 
    907   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    908   protected void deleteTheme(long theme) {
    909     // no op
    910   }
    911 
    912   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    913   public static void applyThemeStyle(int themePtr, int styleRes, boolean force) {
    914     applyThemeStyle((long) themePtr, styleRes, force);
    915   }
    916 
    917   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    918   public static void applyThemeStyle(long themePtr, int styleRes, boolean force) {
    919     NativeTheme nativeTheme = getNativeTheme(themePtr);
    920     Style style = nativeTheme.getShadowAssetManager().resolveStyle(styleRes, null);
    921     nativeTheme.themeStyleSet.apply(style, force);
    922   }
    923 
    924   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    925   public static void copyTheme(int destPtr, int sourcePtr) {
    926     copyTheme((long) destPtr, (long) sourcePtr);
    927   }
    928 
    929   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    930   public static void copyTheme(long destPtr, long sourcePtr) {
    931     NativeTheme destNativeTheme = getNativeTheme(destPtr);
    932     NativeTheme sourceNativeTheme = getNativeTheme(sourcePtr);
    933     destNativeTheme.themeStyleSet = sourceNativeTheme.themeStyleSet.copy();
    934   }
    935 
    936   @HiddenApi @Implementation(minSdk = P)
    937   protected static void nativeThemeCopy(long destPtr, long sourcePtr) {
    938     copyTheme(destPtr, sourcePtr);
    939   }
    940 
    941   // BEGIN-INTERNAL
    942   @HiddenApi @Implementation(minSdk = Q)
    943   protected static void nativeThemeCopy(long dstAssetManagerPtr, long dstThemePtr,
    944       long srcAssetManagerPtr, long srcThemePtr) {
    945     copyTheme(dstThemePtr, srcThemePtr);
    946   }
    947   // END-INTERNAL
    948 
    949   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    950   protected static boolean applyStyle(int themeToken, int defStyleAttr, int defStyleRes,
    951       int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
    952     return applyStyle((long)themeToken, defStyleAttr, defStyleRes, (long)xmlParserToken, attrs,
    953         outValues, outIndices);
    954   }
    955 
    956   @HiddenApi @Implementation(minSdk = O, maxSdk = O_MR1)
    957   protected static void applyStyle(long themeToken, int defStyleAttr, int defStyleRes,
    958       long xmlParserToken, int[] inAttrs, int length, long outValuesAddress,
    959       long outIndicesAddress) {
    960     ShadowVMRuntime shadowVMRuntime = Shadow.extract(VMRuntime.getRuntime());
    961     int[] outValues = (int[])shadowVMRuntime.getObjectForAddress(outValuesAddress);
    962     int[] outIndices = (int[])shadowVMRuntime.getObjectForAddress(outIndicesAddress);
    963     applyStyle(themeToken, defStyleAttr, defStyleRes, xmlParserToken, inAttrs,
    964         outValues, outIndices);
    965   }
    966 
    967   @HiddenApi @Implementation(minSdk = P)
    968   protected void applyStyleToTheme(long themePtr, int resId, boolean force) {
    969     applyThemeStyle(themePtr, resId, force);
    970   }
    971 
    972   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
    973   protected static boolean applyStyle(long themeToken, int defStyleAttr, int defStyleRes,
    974       long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
    975     // no-op
    976     return false;
    977   }
    978 
    979   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    980   protected static boolean resolveAttrs(long themeToken,
    981       int defStyleAttr, int defStyleRes, int[] inValues,
    982       int[] attrs, int[] outValues, int[] outIndices) {
    983     // no-op
    984     return false;
    985   }
    986 
    987   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
    988   protected boolean retrieveAttributes(
    989       int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
    990     return retrieveAttributes((long)xmlParserToken, attrs, outValues, outIndices);
    991   }
    992 
    993   @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
    994   protected boolean retrieveAttributes(long xmlParserToken, int[] attrs, int[] outValues,
    995       int[] outIndices) {
    996     return false;
    997   }
    998 
    999   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
   1000   protected static int loadThemeAttributeValue(int themeHandle, int ident,
   1001       TypedValue outValue, boolean resolve) {
   1002     return loadThemeAttributeValue((long) themeHandle, ident, outValue, resolve);
   1003   }
   1004 
   1005   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
   1006   protected static int loadThemeAttributeValue(long themeHandle, int ident,
   1007       TypedValue outValue, boolean resolve) {
   1008     // no-op
   1009     return 0;
   1010   }
   1011 
   1012   /////////////////////////
   1013 
   1014   Style resolveStyle(int resId, Style themeStyleSet) {
   1015     return resolveStyle(getResName(resId), themeStyleSet);
   1016   }
   1017 
   1018   private Style resolveStyle(@Nonnull ResName themeStyleName, Style themeStyleSet) {
   1019     TypedResource themeStyleResource = resourceTable.getValue(themeStyleName, config);
   1020     if (themeStyleResource == null) return null;
   1021     StyleData themeStyleData = (StyleData) themeStyleResource.getData();
   1022     if (themeStyleSet == null) {
   1023       themeStyleSet = new ThemeStyleSet();
   1024     }
   1025     return new StyleResolver(resourceTable, legacyShadowOf(AssetManager.getSystem()).getResourceTable(),
   1026         themeStyleData, themeStyleSet, themeStyleName, config);
   1027   }
   1028 
   1029   private TypedResource getAndResolve(int resId, ResTable_config config, boolean resolveRefs) {
   1030     TypedResource value = resourceTable.getValue(resId, config);
   1031     if (resolveRefs) {
   1032       value = resolve(value, config, resId);
   1033     }
   1034     return value;
   1035   }
   1036 
   1037   TypedResource resolve(TypedResource value, ResTable_config config, int resId) {
   1038     return resolveResourceValue(value, config, resId);
   1039   }
   1040 
   1041   protected ResName resolveResName(ResName resName, ResTable_config config) {
   1042     TypedResource value = resourceTable.getValue(resName, config);
   1043     return resolveResource(value, config, resName);
   1044   }
   1045 
   1046   // todo: DRY up #resolveResource vs #resolveResourceValue
   1047   private ResName resolveResource(TypedResource value, ResTable_config config, ResName resName) {
   1048     while (value != null && value.isReference()) {
   1049       String s = value.asString();
   1050       if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) {
   1051         value = null;
   1052       } else {
   1053         String refStr = s.substring(1).replace("+", "");
   1054         resName = ResName.qualifyResName(refStr, resName);
   1055         value = resourceTable.getValue(resName, config);
   1056       }
   1057     }
   1058 
   1059     return resName;
   1060   }
   1061 
   1062   private TypedResource resolveResourceValue(TypedResource value, ResTable_config config, ResName resName) {
   1063     while (value != null && value.isReference()) {
   1064       String s = value.asString();
   1065       if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) {
   1066         value = null;
   1067       } else {
   1068         String refStr = s.substring(1).replace("+", "");
   1069         resName = ResName.qualifyResName(refStr, resName);
   1070         value = resourceTable.getValue(resName, config);
   1071       }
   1072     }
   1073 
   1074     return value;
   1075   }
   1076 
   1077   protected TypedResource resolveResourceValue(TypedResource value, ResTable_config config, int resId) {
   1078     ResName resName = getResName(resId);
   1079     return resolveResourceValue(value, config, resName);
   1080   }
   1081 
   1082   private TypedValue buildTypedValue(AttributeSet set, int resId, int defStyleAttr, Style themeStyleSet, int defStyleRes) {
   1083     /*
   1084      * When determining the final value of a particular attribute, there are four inputs that come into play:
   1085      *
   1086      * 1. Any attribute values in the given AttributeSet.
   1087      * 2. The style resource specified in the AttributeSet (named "style").
   1088      * 3. The default style specified by defStyleAttr and defStyleRes
   1089      * 4. The base values in this theme.
   1090      */
   1091     Style defStyleFromAttr = null;
   1092     Style defStyleFromRes = null;
   1093     Style styleAttrStyle = null;
   1094 
   1095     if (defStyleAttr != 0) {
   1096       // Load the theme attribute for the default style attributes. E.g., attr/buttonStyle
   1097       ResName defStyleName = getResName(defStyleAttr);
   1098 
   1099       // Load the style for the default style attribute. E.g. "@style/Widget.Robolectric.Button";
   1100       AttributeResource defStyleAttribute = themeStyleSet.getAttrValue(defStyleName);
   1101       if (defStyleAttribute != null) {
   1102         while (defStyleAttribute.isStyleReference()) {
   1103           AttributeResource other = themeStyleSet.getAttrValue(defStyleAttribute.getStyleReference());
   1104           if (other == null) {
   1105             throw new RuntimeException("couldn't dereference " + defStyleAttribute);
   1106           }
   1107           defStyleAttribute = other;
   1108         }
   1109 
   1110         if (defStyleAttribute.isResourceReference()) {
   1111           ResName defStyleResName = defStyleAttribute.getResourceReference();
   1112           defStyleFromAttr = resolveStyle(defStyleResName, themeStyleSet);
   1113         }
   1114       }
   1115     }
   1116 
   1117     if (set != null && set.getStyleAttribute() != 0) {
   1118       ResName styleAttributeResName = getResName(set.getStyleAttribute());
   1119       while (styleAttributeResName.type.equals("attr")) {
   1120         AttributeResource attrValue = themeStyleSet.getAttrValue(styleAttributeResName);
   1121         if (attrValue == null) {
   1122           throw new RuntimeException(
   1123               "no value for " + styleAttributeResName.getFullyQualifiedName()
   1124                   + " in " + themeStyleSet);
   1125         }
   1126         if (attrValue.isResourceReference()) {
   1127           styleAttributeResName = attrValue.getResourceReference();
   1128         } else if (attrValue.isStyleReference()) {
   1129           styleAttributeResName = attrValue.getStyleReference();
   1130         }
   1131       }
   1132       styleAttrStyle = resolveStyle(styleAttributeResName, themeStyleSet);
   1133     }
   1134 
   1135     if (defStyleRes != 0) {
   1136       ResName resName = getResName(defStyleRes);
   1137       if (resName.type.equals("attr")) {
   1138         // todo: this should be a style resId, not an attr
   1139         System.out.println("WARN: " + resName.getFullyQualifiedName() + " should be a style resId");
   1140         // AttributeResource attributeValue = findAttributeValue(defStyleRes, set, styleAttrStyle, defStyleFromAttr, defStyleFromAttr, themeStyleSet);
   1141         // if (attributeValue != null) {
   1142         //   if (attributeValue.isStyleReference()) {
   1143         //     resName = themeStyleSet.getAttrValue(attributeValue.getStyleReference()).getResourceReference();
   1144         //   } else if (attributeValue.isResourceReference()) {
   1145         //     resName = attributeValue.getResourceReference();
   1146         //   }
   1147         // }
   1148       } else if (resName.type.equals("style")) {
   1149         defStyleFromRes = resolveStyle(resName, themeStyleSet);
   1150       }
   1151     }
   1152 
   1153     AttributeResource attribute = findAttributeValue(resId, set, styleAttrStyle, defStyleFromAttr, defStyleFromRes, themeStyleSet);
   1154     while (attribute != null && attribute.isStyleReference()) {
   1155       ResName otherAttrName = attribute.getStyleReference();
   1156       if (attribute.resName.equals(otherAttrName)) {
   1157         Logger.info("huh... circular reference for %s?", attribute.resName.getFullyQualifiedName());
   1158         return null;
   1159       }
   1160       ResName resName = resourceTable.getResName(resId);
   1161 
   1162       AttributeResource otherAttr = themeStyleSet.getAttrValue(otherAttrName);
   1163       if (otherAttr == null) {
   1164         strictError("no such attr %s in %s while resolving value for %s", attribute.value, themeStyleSet, resName.getFullyQualifiedName());
   1165         attribute = null;
   1166       } else {
   1167         attribute = new AttributeResource(resName, otherAttr.value, otherAttr.contextPackageName);
   1168       }
   1169     }
   1170 
   1171     if (attribute == null || attribute.isNull()) {
   1172       return null;
   1173     } else {
   1174       TypedValue typedValue = new TypedValue();
   1175       convertAndFill(attribute, typedValue, config, true);
   1176       return typedValue;
   1177     }
   1178   }
   1179 
   1180   private void strictError(String message, Object... args) {
   1181     if (strictErrors) {
   1182       throw new RuntimeException(String.format(message, args));
   1183     } else {
   1184       Logger.strict(message, args);
   1185     }
   1186   }
   1187 
   1188   TypedArray attrsToTypedArray(Resources resources, AttributeSet set, int[] attrs, int defStyleAttr, long nativeTheme, int defStyleRes) {
   1189     CharSequence[] stringData = new CharSequence[attrs.length];
   1190     int[] data = new int[attrs.length * STYLE_NUM_ENTRIES];
   1191     int[] indices = new int[attrs.length + 1];
   1192     int nextIndex = 0;
   1193 
   1194     Style themeStyleSet = nativeTheme == 0
   1195         ? new EmptyStyle()
   1196         : getNativeTheme(nativeTheme).themeStyleSet;
   1197 
   1198     for (int i = 0; i < attrs.length; i++) {
   1199       int offset = i * STYLE_NUM_ENTRIES;
   1200 
   1201       TypedValue typedValue = buildTypedValue(set, attrs[i], defStyleAttr, themeStyleSet, defStyleRes);
   1202       if (typedValue != null) {
   1203         //noinspection PointlessArithmeticExpression
   1204         data[offset + STYLE_TYPE] = typedValue.type;
   1205         data[offset + STYLE_DATA] = typedValue.type == TypedValue.TYPE_STRING ? i : typedValue.data;
   1206         data[offset + STYLE_ASSET_COOKIE] = typedValue.assetCookie;
   1207         data[offset + STYLE_RESOURCE_ID] = typedValue.resourceId;
   1208         data[offset + STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations;
   1209         data[offset + STYLE_DENSITY] = typedValue.density;
   1210         stringData[i] = typedValue.string;
   1211 
   1212         indices[nextIndex + 1] = i;
   1213         nextIndex++;
   1214       }
   1215     }
   1216 
   1217     indices[0] = nextIndex;
   1218 
   1219     TypedArray typedArray = ShadowTypedArray.create(resources, attrs, data, indices, nextIndex, stringData);
   1220     if (set != null) {
   1221       ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray);
   1222       shadowTypedArray.positionDescription = set.getPositionDescription();
   1223     }
   1224     return typedArray;
   1225   }
   1226 
   1227   private AttributeResource findAttributeValue(int resId, AttributeSet attributeSet, Style styleAttrStyle, Style defStyleFromAttr, Style defStyleFromRes, @Nonnull Style themeStyleSet) {
   1228     if (attributeSet != null) {
   1229       for (int i = 0; i < attributeSet.getAttributeCount(); i++) {
   1230         if (attributeSet.getAttributeNameResource(i) == resId) {
   1231           String attributeValue;
   1232           try {
   1233             attributeValue = attributeSet.getAttributeValue(i);
   1234           } catch (IndexOutOfBoundsException e) {
   1235             // type is TypedValue.TYPE_NULL, ignore...
   1236             continue;
   1237           }
   1238           if (attributeValue != null) {
   1239             String defaultPackageName = ResourceIds.isFrameworkResource(resId) ? "android" : RuntimeEnvironment.application.getPackageName();
   1240             ResName resName = ResName.qualifyResName(attributeSet.getAttributeName(i), defaultPackageName, "attr");
   1241             Integer referenceResId = null;
   1242             if (AttributeResource.isResourceReference(attributeValue)) {
   1243               referenceResId = attributeSet.getAttributeResourceValue(i, -1);
   1244               // binary AttributeSet references have a string value of @resId rather than fully qualified resource name
   1245               if (referenceResId != 0) {
   1246                 ResName refResName = resourceTable.getResName(referenceResId);
   1247                 if (refResName != null) {
   1248                   attributeValue = "@" + refResName.getFullyQualifiedName();
   1249                 }
   1250               }
   1251             }
   1252             return new AttributeResource(resName, attributeValue, "fixme!!!", referenceResId);
   1253           }
   1254         }
   1255       }
   1256     }
   1257 
   1258     ResName attrName = resourceTable.getResName(resId);
   1259     if (attrName == null) return null;
   1260 
   1261     if (styleAttrStyle != null) {
   1262       AttributeResource attribute = styleAttrStyle.getAttrValue(attrName);
   1263       if (attribute != null) {
   1264         return attribute;
   1265       }
   1266     }
   1267 
   1268     // else if attr in defStyleFromAttr, use its value
   1269     if (defStyleFromAttr != null) {
   1270       AttributeResource attribute = defStyleFromAttr.getAttrValue(attrName);
   1271       if (attribute != null) {
   1272         return attribute;
   1273       }
   1274     }
   1275 
   1276     if (defStyleFromRes != null) {
   1277       AttributeResource attribute = defStyleFromRes.getAttrValue(attrName);
   1278       if (attribute != null) {
   1279         return attribute;
   1280       }
   1281     }
   1282 
   1283     // else if attr in theme, use its value
   1284     return themeStyleSet.getAttrValue(attrName);
   1285   }
   1286 
   1287   @Override
   1288   Collection<FsFile> getAllAssetDirs() {
   1289     return assetDirs;
   1290   }
   1291 
   1292   @Nonnull private ResName getResName(int id) {
   1293     ResName resName = resourceTable.getResName(id);
   1294     if (resName == null) {
   1295       throw new Resources.NotFoundException("Resource ID #0x" + Integer.toHexString(id));
   1296     }
   1297     return resName;
   1298   }
   1299 
   1300   @Implementation
   1301   protected String getResourceName(int resid) {
   1302     return getResName(resid).getFullyQualifiedName();
   1303   }
   1304 
   1305   @Implementation
   1306   protected String getResourcePackageName(int resid) {
   1307     return getResName(resid).packageName;
   1308   }
   1309 
   1310   @Implementation
   1311   protected String getResourceTypeName(int resid) {
   1312     return getResName(resid).type;
   1313   }
   1314 
   1315   @Implementation
   1316   protected String getResourceEntryName(int resid) {
   1317     return getResName(resid).name;
   1318   }
   1319 
   1320   @Implementation(maxSdk = O_MR1)
   1321   protected int getArraySize(int id) {
   1322     return 0;
   1323   }
   1324 
   1325   @Implementation(maxSdk = O_MR1)
   1326   protected int retrieveArray(int id, int[] outValues) {
   1327     return 0;
   1328   }
   1329 
   1330   @Implementation(maxSdk = O_MR1)
   1331   protected Number getNativeStringBlock(int block) {
   1332     throw new IllegalStateException();
   1333   }
   1334 
   1335   @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
   1336   protected final SparseArray<String> getAssignedPackageIdentifiers() {
   1337     return new SparseArray<>();
   1338   }
   1339 
   1340   @Implementation(maxSdk = O_MR1)
   1341   protected int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve) {
   1342     return 0;
   1343   }
   1344 
   1345   @Implementation(maxSdk = O_MR1)
   1346   protected int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, boolean resolve) {
   1347     return 0;
   1348   }
   1349 
   1350   // static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
   1351   @Implementation(minSdk = P)
   1352   protected static void nativeAssetDestroy(long asset_ptr) {
   1353     ShadowArscAssetManager9.nativeAssetDestroy(asset_ptr);
   1354   }
   1355 
   1356   // static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
   1357   @Implementation(minSdk = P)
   1358   protected static int nativeAssetReadChar(long asset_ptr) {
   1359     return ShadowArscAssetManager9.nativeAssetReadChar(asset_ptr);
   1360   }
   1361 
   1362   // static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer,
   1363 //                             jint offset, jint len) {
   1364   @Implementation(minSdk = P)
   1365   protected static int nativeAssetRead(long asset_ptr, byte[] java_buffer, int offset, int len)
   1366       throws IOException {
   1367     return ShadowArscAssetManager9.nativeAssetRead(asset_ptr, java_buffer, offset, len);
   1368   }
   1369 
   1370   // static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset,
   1371 //                              jint whence) {
   1372   @Implementation(minSdk = P)
   1373   protected static long nativeAssetSeek(long asset_ptr, long offset, int whence) {
   1374     return ShadowArscAssetManager9.nativeAssetSeek(asset_ptr, offset, whence);
   1375   }
   1376 
   1377   // static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
   1378   @Implementation(minSdk = P)
   1379   protected static long nativeAssetGetLength(long asset_ptr) {
   1380     return ShadowArscAssetManager9.nativeAssetGetLength(asset_ptr);
   1381   }
   1382 
   1383   // static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
   1384   @Implementation(minSdk = P)
   1385   protected static long nativeAssetGetRemainingLength(long asset_ptr) {
   1386     return ShadowArscAssetManager9.nativeAssetGetRemainingLength(asset_ptr);
   1387   }
   1388 
   1389   // BEGIN-INTERNAL
   1390   @Implementation(minSdk = Build.VERSION_CODES.Q)
   1391   protected static String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid() {
   1392     return new String[0];
   1393   }
   1394   // END-INTERNAL
   1395 
   1396   @Resetter
   1397   public static void reset() {
   1398     // todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for resetters...
   1399     if (useLegacy()) {
   1400       if (RuntimeEnvironment.getApiLevel() >= P) {
   1401         ReflectionHelpers.setStaticField(AssetManager.class, "sSystemApkAssetsSet", null);
   1402         ReflectionHelpers.setStaticField(AssetManager.class, "sSystemApkAssets", null);
   1403       }
   1404       ReflectionHelpers.setStaticField(AssetManager.class, "sSystem", null);
   1405     }
   1406   }
   1407 
   1408 }
   1409