• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
NativeTheme(ThemeStyleSet themeStyleSet)127     public NativeTheme(ThemeStyleSet themeStyleSet) {
128       this.themeStyleSet = themeStyleSet;
129     }
130 
getShadowAssetManager()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 
convertAndFill(AttributeResource attribute, TypedValue outValue, ResTable_config config, boolean resolveRefs)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 
getAttrTypeData(ResName resName)240   public TypedResource getAttrTypeData(ResName resName) {
241     return resourceTable.getValue(resName, config);
242   }
243 
244   @Implementation
__constructor__()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
__constructor__(boolean isSystem)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)
nativeCreate()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)
init()273   protected void init() {
274     // no op
275   }
276 
277   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
init(boolean isSystem)278   protected void init(boolean isSystem) {
279     // no op
280   }
281 
getResourceTable()282   protected ResourceTable getResourceTable() {
283     return resourceTable;
284   }
285 
286   @HiddenApi @Implementation
getResourceText(int ident)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
getResourceBagText(int ident, int bagEntryId)294   public CharSequence getResourceBagText(int ident, int bagEntryId) {
295     throw new UnsupportedOperationException(); // todo
296   }
297 
298   @HiddenApi @Implementation(maxSdk = O_MR1)
getStringBlockCount()299   protected int getStringBlockCount() {
300     return 0;
301   }
302 
303   @HiddenApi @Implementation
getResourceStringArray(final int id)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
getResourceIdentifier(String name, String defType, String defPackage)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
getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs)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 
getConverter(TypedResource value)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
getResourceTextArray(int resId)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)
getThemeValue(int themePtr, int ident, TypedValue outValue, boolean resolveRefs)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)
getThemeValue(long themePtr, int ident, TypedValue outValue, boolean resolveRefs)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)
ensureStringBlocks()378   protected Object ensureStringBlocks() {
379     return null;
380   }
381 
382   @Implementation
open(String fileName)383   protected final InputStream open(String fileName) throws IOException {
384     return findAssetFile(fileName).getInputStream();
385   }
386 
387   @Implementation
open(String fileName, int accessMode)388   protected final InputStream open(String fileName, int accessMode) throws IOException {
389     return findAssetFile(fileName).getInputStream();
390   }
391 
392   @Implementation
openFd(String fileName)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 
findAssetFile(String fileName)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    */
getFileFromZip(File file)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
list(String path)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)
openAsset(String fileName, int mode)472   protected Number openAsset(String fileName, int mode) throws FileNotFoundException {
473     return 0;
474   }
475 
476   @HiddenApi @Implementation(maxSdk = O_MR1)
openAssetFd(String fileName, long[] outOffsets)477   protected ParcelFileDescriptor openAssetFd(String fileName, long[] outOffsets) throws IOException {
478     return null;
479   }
480 
481   @HiddenApi @Implementation
openNonAsset(int cookie, String fileName, int accessMode)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)
openNonAssetNative(int cookie, String fileName, int accessMode)510   protected Number openNonAssetNative(int cookie, String fileName, int accessMode)
511       throws FileNotFoundException {
512     throw new IllegalStateException();
513   }
514 
qualifyFromNonAssetFileName(String fileName)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
openNonAssetFd(int cookie, String fileName)527   public final AssetFileDescriptor openNonAssetFd(int cookie, String fileName) throws IOException {
528     throw new IllegalStateException();
529   }
530 
531   @HiddenApi @Implementation(maxSdk = O_MR1)
openNonAssetFdNative(int cookie, String fileName, long[] outOffsets)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)
openXmlAssetNative(int cookie, String fileName)538   protected Number openXmlAssetNative(int cookie, String fileName) throws FileNotFoundException {
539     throw new IllegalStateException();
540   }
541 
542   @Implementation
openXmlResourceParser(int cookie, String fileName)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)
seekAsset(int asset, long offset, int whence)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)
seekAsset(long asset, long offset, int whence)558   protected long seekAsset(long asset, long offset, int whence) {
559     return 0;
560   }
561 
562   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
getAssetLength(int asset)563   protected final long getAssetLength(int asset) {
564     return getAssetLength((long) asset);
565   }
566 
567   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
getAssetLength(long asset)568   protected long getAssetLength(long asset) {
569     return 0;
570   }
571 
572   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
getAssetRemainingLength(int asset)573   protected final long getAssetRemainingLength(int asset) {
574     return getAssetRemainingLength((long) asset);
575   }
576 
577   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
getAssetRemainingLength(long assetHandle)578   protected long getAssetRemainingLength(long assetHandle) {
579     return 0;
580   }
581 
582   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
destroyAsset(int asset)583   protected final void destroyAsset(int asset) {
584     destroyAsset((long) asset);
585   }
586 
587   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
destroyAsset(long asset)588   protected void destroyAsset(long asset) {
589     // no op
590   }
591 
loadXmlResourceParser(int resId, String type)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 
getXmlResourceParser(ResourceTable resourceProvider, XmlBlock block, String packageName)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
addAssetPath(String path)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)
addAssetPathNative(String path)622   final protected int addAssetPathNative(String path) {
623     return addAssetPathNative(path, false);
624   }
625 
626   @HiddenApi @Implementation(minSdk = N, maxSdk = O_MR1)
addAssetPathNative(String path, boolean appAsLib)627   protected int addAssetPathNative(String path, boolean appAsLib) {
628     return 0;
629   }
630 
631   @HiddenApi @Implementation(minSdk = P)
setApkAssets(Object apkAssetsObject, Object invalidateCachesObject)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 
getFsFileFromPath(String property)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
isUpToDate()656   public boolean isUpToDate() {
657     return true;
658   }
659 
660   @HiddenApi @Implementation(maxSdk = M)
setLocale(String locale)661   public void setLocale(String locale) {
662   }
663 
664   @Implementation
getLocales()665   protected String[] getLocales() {
666     return new String[0]; // todo
667   }
668 
669   @HiddenApi @Implementation(maxSdk = N_MR1)
setConfiguration(int mcc, int mnc, String locale, int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int sdkVersion)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)
setConfiguration(int mcc, int mnc, String locale, int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion)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)
getArrayIntResource(int resId)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)
getResourceIntArray(int resId)743   protected int[] getResourceIntArray(int resId) {
744     return getArrayIntResource(resId);
745   }
746 
747   @HiddenApi @Implementation(maxSdk = O_MR1)
getArrayStringResource(int arrayResId)748   protected String[] getArrayStringResource(int arrayResId) {
749     return new String[0];
750   }
751 
752   @HiddenApi @Implementation(maxSdk = O_MR1)
getArrayStringInfo(int arrayResId)753   protected int[] getArrayStringInfo(int arrayResId) {
754     return new int[0];
755   }
756 
757   @HiddenApi @Implementation(maxSdk = O_MR1)
newTheme()758   protected Number newTheme() {
759     return null;
760   }
761 
getTypedArrayResource(Resources resources, int resId)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 
getTypedArray(Resources resources, List<TypedResource> typedResources, int resId)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 
getResourceType(TypedResource typedResource)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
createTheme()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)
dumpTheme(long theme, int priority, String tag, String prefix)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)
releaseTheme(int themePtr)880   public void releaseTheme(int themePtr) {
881     // no op
882   }
883 
getNativeTheme(long themePtr)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)
releaseTheme(long themePtr)896   public void releaseTheme(long themePtr) {
897     synchronized (nativeThemes) {
898       nativeThemes.remove(themePtr);
899     }
900   }
901 
902   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
deleteTheme(int theme)903   protected void deleteTheme(int theme) {
904     deleteTheme((long) theme);
905   }
906 
907   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
deleteTheme(long theme)908   protected void deleteTheme(long theme) {
909     // no op
910   }
911 
912   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
applyThemeStyle(int themePtr, int styleRes, boolean force)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)
applyThemeStyle(long themePtr, int styleRes, boolean force)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)
copyTheme(int destPtr, int sourcePtr)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)
copyTheme(long destPtr, long sourcePtr)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)
nativeThemeCopy(long destPtr, long sourcePtr)937   protected static void nativeThemeCopy(long destPtr, long sourcePtr) {
938     copyTheme(destPtr, sourcePtr);
939   }
940 
941   // BEGIN-INTERNAL
942   @HiddenApi @Implementation(minSdk = Q)
nativeThemeCopy(long dstAssetManagerPtr, long dstThemePtr, long srcAssetManagerPtr, long srcThemePtr)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)
applyStyle(int themeToken, int defStyleAttr, int defStyleRes, int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)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)
applyStyle(long themeToken, int defStyleAttr, int defStyleRes, long xmlParserToken, int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress)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)
applyStyleToTheme(long themePtr, int resId, boolean force)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)
applyStyle(long themeToken, int defStyleAttr, int defStyleRes, long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)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)
resolveAttrs(long themeToken, int defStyleAttr, int defStyleRes, int[] inValues, int[] attrs, int[] outValues, int[] outIndices)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)
retrieveAttributes( int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)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)
retrieveAttributes(long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)994   protected boolean retrieveAttributes(long xmlParserToken, int[] attrs, int[] outValues,
995       int[] outIndices) {
996     return false;
997   }
998 
999   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
loadThemeAttributeValue(int themeHandle, int ident, TypedValue outValue, boolean resolve)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)
loadThemeAttributeValue(long themeHandle, int ident, TypedValue outValue, boolean resolve)1006   protected static int loadThemeAttributeValue(long themeHandle, int ident,
1007       TypedValue outValue, boolean resolve) {
1008     // no-op
1009     return 0;
1010   }
1011 
1012   /////////////////////////
1013 
resolveStyle(int resId, Style themeStyleSet)1014   Style resolveStyle(int resId, Style themeStyleSet) {
1015     return resolveStyle(getResName(resId), themeStyleSet);
1016   }
1017 
resolveStyle(@onnull ResName themeStyleName, Style themeStyleSet)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 
getAndResolve(int resId, ResTable_config config, boolean resolveRefs)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 
resolve(TypedResource value, ResTable_config config, int resId)1037   TypedResource resolve(TypedResource value, ResTable_config config, int resId) {
1038     return resolveResourceValue(value, config, resId);
1039   }
1040 
resolveResName(ResName resName, ResTable_config config)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
resolveResource(TypedResource value, ResTable_config config, ResName resName)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 
resolveResourceValue(TypedResource value, ResTable_config config, ResName resName)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 
resolveResourceValue(TypedResource value, ResTable_config config, int resId)1077   protected TypedResource resolveResourceValue(TypedResource value, ResTable_config config, int resId) {
1078     ResName resName = getResName(resId);
1079     return resolveResourceValue(value, config, resName);
1080   }
1081 
buildTypedValue(AttributeSet set, int resId, int defStyleAttr, Style themeStyleSet, int defStyleRes)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 
strictError(String message, Object... args)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 
attrsToTypedArray(Resources resources, AttributeSet set, int[] attrs, int defStyleAttr, long nativeTheme, int defStyleRes)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 
findAttributeValue(int resId, AttributeSet attributeSet, Style styleAttrStyle, Style defStyleFromAttr, Style defStyleFromRes, @Nonnull Style themeStyleSet)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
getAllAssetDirs()1288   Collection<FsFile> getAllAssetDirs() {
1289     return assetDirs;
1290   }
1291 
getResName(int id)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
getResourceName(int resid)1301   protected String getResourceName(int resid) {
1302     return getResName(resid).getFullyQualifiedName();
1303   }
1304 
1305   @Implementation
getResourcePackageName(int resid)1306   protected String getResourcePackageName(int resid) {
1307     return getResName(resid).packageName;
1308   }
1309 
1310   @Implementation
getResourceTypeName(int resid)1311   protected String getResourceTypeName(int resid) {
1312     return getResName(resid).type;
1313   }
1314 
1315   @Implementation
getResourceEntryName(int resid)1316   protected String getResourceEntryName(int resid) {
1317     return getResName(resid).name;
1318   }
1319 
1320   @Implementation(maxSdk = O_MR1)
getArraySize(int id)1321   protected int getArraySize(int id) {
1322     return 0;
1323   }
1324 
1325   @Implementation(maxSdk = O_MR1)
retrieveArray(int id, int[] outValues)1326   protected int retrieveArray(int id, int[] outValues) {
1327     return 0;
1328   }
1329 
1330   @Implementation(maxSdk = O_MR1)
getNativeStringBlock(int block)1331   protected Number getNativeStringBlock(int block) {
1332     throw new IllegalStateException();
1333   }
1334 
1335   @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
getAssignedPackageIdentifiers()1336   protected final SparseArray<String> getAssignedPackageIdentifiers() {
1337     return new SparseArray<>();
1338   }
1339 
1340   @Implementation(maxSdk = O_MR1)
loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve)1341   protected int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve) {
1342     return 0;
1343   }
1344 
1345   @Implementation(maxSdk = O_MR1)
loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, boolean resolve)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)
nativeAssetDestroy(long asset_ptr)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)
nativeAssetReadChar(long asset_ptr)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)
nativeAssetRead(long asset_ptr, byte[] java_buffer, int offset, int len)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)
nativeAssetSeek(long asset_ptr, long offset, int whence)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)
nativeAssetGetLength(long asset_ptr)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)
nativeAssetGetRemainingLength(long asset_ptr)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)
nativeCreateIdmapsForStaticOverlaysTargetingAndroid()1391   protected static String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid() {
1392     return new String[0];
1393   }
1394   // END-INTERNAL
1395 
1396   @Resetter
reset()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