• 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_MR1;
8 import static android.os.Build.VERSION_CODES.O;
9 import static android.os.Build.VERSION_CODES.O_MR1;
10 import static android.os.Build.VERSION_CODES.P;
11 import static org.robolectric.res.android.Asset.SEEK_CUR;
12 import static org.robolectric.res.android.Asset.SEEK_SET;
13 import static org.robolectric.res.android.AttributeResolution.kThrowOnBadId;
14 import static org.robolectric.res.android.Errors.BAD_INDEX;
15 import static org.robolectric.res.android.Errors.NO_ERROR;
16 import static org.robolectric.res.android.Util.ALOGV;
17 import static org.robolectric.res.android.Util.isTruthy;
18 
19 import android.content.res.AssetManager;
20 import android.os.Build.VERSION_CODES;
21 import android.os.ParcelFileDescriptor;
22 import android.util.SparseArray;
23 import android.util.TypedValue;
24 import com.google.common.annotations.VisibleForTesting;
25 import com.google.common.base.Preconditions;
26 import com.google.common.base.Strings;
27 import dalvik.system.VMRuntime;
28 import java.io.File;
29 import java.io.FileDescriptor;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import org.robolectric.RuntimeEnvironment;
38 import org.robolectric.annotation.HiddenApi;
39 import org.robolectric.annotation.Implementation;
40 import org.robolectric.annotation.Implements;
41 import org.robolectric.annotation.RealObject;
42 import org.robolectric.annotation.Resetter;
43 import org.robolectric.res.Fs;
44 import org.robolectric.res.FsFile;
45 import org.robolectric.res.android.Asset;
46 import org.robolectric.res.android.Asset.AccessMode;
47 import org.robolectric.res.android.AssetDir;
48 import org.robolectric.res.android.AssetPath;
49 import org.robolectric.res.android.AttributeResolution;
50 import org.robolectric.res.android.CppAssetManager;
51 import org.robolectric.res.android.DataType;
52 import org.robolectric.res.android.DynamicRefTable;
53 import org.robolectric.res.android.Ref;
54 import org.robolectric.res.android.Registries;
55 import org.robolectric.res.android.ResStringPool;
56 import org.robolectric.res.android.ResTable;
57 import org.robolectric.res.android.ResTable.ResourceName;
58 import org.robolectric.res.android.ResTable.bag_entry;
59 import org.robolectric.res.android.ResTableTheme;
60 import org.robolectric.res.android.ResTable_config;
61 import org.robolectric.res.android.ResXMLParser;
62 import org.robolectric.res.android.ResXMLTree;
63 import org.robolectric.res.android.ResourceTypes.Res_value;
64 import org.robolectric.res.android.String8;
65 import org.robolectric.shadow.api.Shadow;
66 import org.robolectric.shadows.ShadowAssetManager.Picker;
67 import org.robolectric.util.ReflectionHelpers;
68 
69 // native method impls transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_util_AssetManager.cpp
70 @Implements(value = AssetManager.class, maxSdk = VERSION_CODES.O_MR1,
71     shadowPicker = Picker.class)
72 public class ShadowArscAssetManager extends ShadowAssetManager.ArscBase {
73 
74   private static final int STYLE_NUM_ENTRIES = 6;
75   private static final int STYLE_TYPE = 0;
76   private static final int STYLE_DATA = 1;
77   private static final int STYLE_ASSET_COOKIE = 2;
78   private static final int STYLE_RESOURCE_ID = 3;
79   private static final int STYLE_CHANGING_CONFIGURATIONS = 4;
80   private static final int STYLE_DENSITY = 5;
81 
82   @RealObject
83   protected AssetManager realObject;
84 
85   private CppAssetManager cppAssetManager;
86 
87   @Resetter
reset()88   public static void reset() {
89     // todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for resetters...
90     if (!useLegacy() && RuntimeEnvironment.getApiLevel() < P) {
91       ReflectionHelpers.setStaticField(AssetManager.class, "sSystem", null);
92       // NATIVE_THEME_REGISTRY.clear();
93       // nativeXMLParserRegistry.clear(); // todo: shouldn't these be freed explicitly? [yes! xw]
94       // NATIVE_ASSET_REGISTRY.clear();
95     }
96   }
97 
98   @Implementation
list(String path)99   protected final String[] list(String path) throws IOException {
100     CppAssetManager am = assetManagerForJavaObject();
101 
102     String fileName8 = path;
103     if (fileName8 == null) {
104       return null;
105     }
106 
107     AssetDir dir = am.openDir(fileName8);
108 
109     if (dir == null) {
110       throw new FileNotFoundException(fileName8);
111     }
112 
113 
114     int N = dir.getFileCount();
115 
116     String[] array = new String[dir.getFileCount()];
117 
118     for (int i=0; i<N; i++) {
119       String8 name = dir.getFileName(i);
120       array[i] = name.string();
121     }
122 
123     return array;
124   }
125 
126   // @HiddenApi @Implementation(minSdk = VERSION_CODES.P)
127   // public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) {
128   //   throw new UnsupportedOperationException("implement me");
129   // }
130 
131   @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)132   final public void setConfiguration(int mcc, int mnc, String locale,
133       int orientation, int touchscreen, int density, int keyboard,
134       int keyboardHidden, int navigation, int screenWidth, int screenHeight,
135       int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
136       int screenLayout, int uiMode, int sdkVersion) {
137     setConfiguration(mcc, mnc, locale,
138         orientation, touchscreen, density, keyboard,
139         keyboardHidden, navigation, screenWidth, screenHeight,
140         smallestScreenWidthDp, screenWidthDp, screenHeightDp,
141         screenLayout, uiMode, 0, sdkVersion);
142   }
143 
144   @HiddenApi @Implementation(minSdk = 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 sdkVersion)145   public void setConfiguration(int mcc, int mnc, String locale,
146       int orientation, int touchscreen, int density, int keyboard,
147       int keyboardHidden, int navigation, int screenWidth, int screenHeight,
148       int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
149       int screenLayout, int uiMode, int colorMode, int sdkVersion) {
150     CppAssetManager am = assetManagerForJavaObject();
151     if (am == null) {
152       return;
153     }
154 
155     ResTable_config config = new ResTable_config();
156 //    memset(&config, 0, sizeof(config));
157 
158 //    const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;
159 
160     // Constants duplicated from Java class android.content.res.Configuration.
161     int kScreenLayoutRoundMask = 0x300;
162     int kScreenLayoutRoundShift = 8;
163 
164     config.mcc = mcc;
165     config.mnc = mnc;
166     config.orientation = orientation;
167     config.touchscreen = touchscreen;
168     config.density = density;
169     config.keyboard = keyboard;
170     config.inputFlags = keyboardHidden;
171     config.navigation = navigation;
172     config.screenWidth = screenWidth;
173     config.screenHeight = screenHeight;
174     config.smallestScreenWidthDp = smallestScreenWidthDp;
175     config.screenWidthDp = screenWidthDp;
176     config.screenHeightDp = screenHeightDp;
177     config.screenLayout = screenLayout;
178     config.uiMode = uiMode;
179     config.colorMode = (byte) colorMode;
180     config.sdkVersion = sdkVersion;
181     config.minorVersion = 0;
182 
183     // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
184     // in C++. We must extract the round qualifier out of the Java screenLayout and put it
185     // into screenLayout2.
186     config.screenLayout2 =
187         (byte) ((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
188 
189     am.setConfiguration(config, locale);
190 
191 //    if (locale != null) env->ReleaseStringUTFChars(locale, locale8);
192   }
193 
194   @HiddenApi @Implementation
dumpTheme(long theme, int priority, String tag, String prefix)195   protected static void dumpTheme(long theme, int priority, String tag, String prefix) {
196     throw new UnsupportedOperationException("not yet implemented");
197   }
198 
199   @Implementation
getResourceName(int resid)200   protected String getResourceName(int resid) {
201     CppAssetManager am = assetManagerForJavaObject();
202 
203     ResourceName name = new ResourceName();
204     if (!am.getResources().getResourceName(resid, true, name)) {
205       return null;
206     }
207 
208     StringBuilder str = new StringBuilder();
209     if (name.packageName != null) {
210       str.append(name.packageName.trim());
211     }
212     if (name.type != null) {
213       if (str.length() > 0) {
214         char div = ':';
215         str.append(div);
216       }
217       str.append(name.type);
218     }
219     if (name.name != null) {
220       if (str.length() > 0) {
221         char div = '/';
222         str.append(div);
223       }
224       str.append(name.name);
225     }
226     return str.toString();
227   }
228 
229   @Implementation
getResourcePackageName(int resid)230   protected String getResourcePackageName(int resid) {
231     CppAssetManager cppAssetManager = assetManagerForJavaObject();
232 
233     ResourceName name = new ResourceName();
234     if (!cppAssetManager.getResources().getResourceName(resid, true, name)) {
235       return null;
236     }
237 
238     return name.packageName.trim();
239   }
240 
241   @Implementation
getResourceTypeName(int resid)242   protected String getResourceTypeName(int resid) {
243     CppAssetManager cppAssetManager = assetManagerForJavaObject();
244 
245     ResourceName name = new ResourceName();
246     if (!cppAssetManager.getResources().getResourceName(resid, true, name)) {
247       return null;
248     }
249 
250     return name.type;
251   }
252 
253   @Implementation
getResourceEntryName(int resid)254   protected String getResourceEntryName(int resid) {
255     CppAssetManager cppAssetManager = assetManagerForJavaObject();
256 
257     ResourceName name = new ResourceName();
258     if (!cppAssetManager.getResources().getResourceName(resid, true, name)) {
259       return null;
260     }
261 
262     return name.name;
263   }
264 
265   //////////// native method implementations
266 
267 //  public native final String[] list(String path)
268 //      throws IOException;
269 
270 //  @HiddenApi @Implementation(minSdk = VERSION_CODES.P)
271 //  public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) {
272 //    throw new UnsupportedOperationException("implement me");
273 //  }
274 //
275 
276   @HiddenApi @Implementation(maxSdk = VERSION_CODES.JELLY_BEAN_MR1)
addAssetPath(String path)277   public int addAssetPath(String path) {
278     return addAssetPathNative(path);
279   }
280 
281   @HiddenApi @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M)
addAssetPathNative(String path)282   final protected int addAssetPathNative(String path) {
283     return addAssetPathNative(path, false);
284   }
285 
286   @HiddenApi @Implementation(minSdk = VERSION_CODES.N)
addAssetPathNative(String path, boolean appAsLib)287   protected int addAssetPathNative(String path, boolean appAsLib) {
288     if (Strings.isNullOrEmpty(path)) {
289       return 0;
290     }
291 
292     CppAssetManager am = assetManagerForJavaObject();
293     if (am == null) {
294       return 0;
295     }
296     final Ref<Integer> cookie = new Ref<>(null);
297     boolean res = am.addAssetPath(new String8(path), cookie, appAsLib);
298     return (res) ? cookie.get() : 0;
299   }
300 
301   @HiddenApi @Implementation
getResourceIdentifier(String name, String defType, String defPackage)302   public int getResourceIdentifier(String name, String defType, String defPackage) {
303     if (Strings.isNullOrEmpty(name)) {
304       return 0;
305     }
306     CppAssetManager am = assetManagerForJavaObject();
307     if (am == null) {
308       return 0;
309     }
310 
311     int ident = am.getResources().identifierForName(name, defType, defPackage);
312 
313     return ident;
314   }
315 
316   @HiddenApi @Implementation
openAsset(String fileName, int mode)317   protected final Number openAsset(String fileName, int mode) throws FileNotFoundException {
318     CppAssetManager am = assetManagerForJavaObject();
319 
320     ALOGV("openAsset in %s", am);
321 
322     String fileName8 = fileName;
323     if (fileName8 == null) {
324       throw new IllegalArgumentException("Empty file name");
325     }
326 
327     if (mode != AccessMode.ACCESS_UNKNOWN.mode() && mode != AccessMode.ACCESS_RANDOM.mode()
328         && mode != AccessMode.ACCESS_STREAMING.mode() && mode != AccessMode.ACCESS_BUFFER.mode()) {
329       throw new IllegalArgumentException("Bad access mode");
330     }
331 
332     Asset a = am.open(fileName8, AccessMode.fromInt(mode));
333 
334     if (a == null) {
335       throw new FileNotFoundException(fileName8);
336     }
337 
338     //printf("Created Asset Stream: %p\n", a);
339 
340     return RuntimeEnvironment.castNativePtr(Registries.NATIVE_ASSET_REGISTRY.register(a));
341   }
342 
343   @HiddenApi @Implementation
openAssetFd(String fileName, long[] outOffsets)344   protected ParcelFileDescriptor openAssetFd(String fileName, long[] outOffsets) throws IOException {
345     CppAssetManager am = assetManagerForJavaObject();
346 
347     ALOGV("openAssetFd in %s", am);
348 
349     String fileName8 = fileName;
350     if (fileName8 == null) {
351       return null;
352     }
353 
354     Asset a = am.open(fileName8, Asset.AccessMode.ACCESS_RANDOM);
355 
356     if (a == null) {
357       throw new FileNotFoundException(fileName8);
358     }
359 
360     return returnParcelFileDescriptor(a, outOffsets);
361   }
362 
363   @HiddenApi @Implementation
openNonAssetNative(int cookie, String fileName, int accessMode)364   protected final Number openNonAssetNative(int cookie, String fileName,
365       int accessMode) throws FileNotFoundException {
366     CppAssetManager am = assetManagerForJavaObject();
367     if (am == null) {
368       return RuntimeEnvironment.castNativePtr(0);
369     }
370     ALOGV("openNonAssetNative in %s (Java object %s)\n", am, AssetManager.class);
371     String fileName8 = fileName;
372     if (fileName8 == null) {
373       return RuntimeEnvironment.castNativePtr(-1);
374     }
375     AccessMode mode = AccessMode.fromInt(accessMode);
376     if (mode != Asset.AccessMode.ACCESS_UNKNOWN && mode != Asset.AccessMode.ACCESS_RANDOM
377         && mode != Asset.AccessMode.ACCESS_STREAMING && mode != Asset.AccessMode.ACCESS_BUFFER) {
378       throw new IllegalArgumentException("Bad access mode");
379     }
380     Asset a = isTruthy(cookie)
381         ? am.openNonAsset(cookie, fileName8, mode)
382         : am.openNonAsset(fileName8, mode, null);
383     if (a == null) {
384       throw new FileNotFoundException(fileName8);
385     }
386     long assetId = Registries.NATIVE_ASSET_REGISTRY.register(a);
387     // todo: something better than this [xw]
388     a.onClose = () -> destroyAsset(assetId);
389     //printf("Created Asset Stream: %p\n", a);
390     return RuntimeEnvironment.castNativePtr(assetId);
391   }
392 
393   @HiddenApi @Implementation
openNonAssetFdNative(int cookie, String fileName, long[] outOffsets)394   protected ParcelFileDescriptor openNonAssetFdNative(int cookie,
395       String fileName, long[] outOffsets) throws IOException {
396     CppAssetManager am = assetManagerForJavaObject();
397 
398     ALOGV("openNonAssetFd in %s (Java object %s)", am, this);
399 
400     if (fileName == null) {
401       return null;
402     }
403 
404     Asset a = isTruthy(cookie)
405         ? am.openNonAsset(cookie, fileName, Asset.AccessMode.ACCESS_RANDOM)
406         : am.openNonAsset(fileName, Asset.AccessMode.ACCESS_RANDOM, null);
407 
408     if (a == null) {
409       throw new FileNotFoundException(fileName);
410     }
411 
412     //printf("Created Asset Stream: %p\n", a);
413 
414     return returnParcelFileDescriptor(a, outOffsets);
415   }
416 
417   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
destroyAsset(int asset)418   protected final void destroyAsset(int asset) {
419     destroyAsset((long) asset);
420   }
421 
422   @HiddenApi @Implementation(minSdk = LOLLIPOP)
destroyAsset(long asset)423   protected final void destroyAsset(long asset) {
424     Registries.NATIVE_ASSET_REGISTRY.unregister(asset);
425   }
426 
427   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
readAssetChar(int asset)428   protected final int readAssetChar(int asset) {
429     return readAssetChar((long) asset);
430   }
431 
432   @HiddenApi @Implementation(minSdk = LOLLIPOP)
readAssetChar(long asset)433   protected final int readAssetChar(long asset) {
434     Asset a = getAsset(asset);
435     byte[] b = new byte[1];
436     int res = a.read(b, 1);
437     return res == 1 ? b[0] & 0xff : -1;
438   }
439 
440   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
readAsset(int asset, byte[] b, int off, int len)441   protected final int readAsset(int asset, byte[] b, int off, int len) throws IOException {
442     return readAsset((long) asset, b, off, len);
443   }
444 
445   @HiddenApi @Implementation(minSdk = LOLLIPOP)
readAsset(long asset, byte[] bArray, int off, int len)446   protected final int readAsset(long asset, byte[] bArray, int off, int len) throws IOException {
447     Asset a = getAsset(asset);
448 
449     if (a == null || bArray == null) {
450       throw new NullPointerException("asset");
451     }
452 
453     if (len == 0) {
454       return 0;
455     }
456 
457     int bLen = bArray.length;
458     if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
459       throw new IndexOutOfBoundsException();
460     }
461 
462     byte[] b = bArray;
463     int res = a.read(b, off, len);
464 
465     if (res > 0) return res;
466 
467     if (res < 0) {
468       throw new IOException();
469     }
470     return -1;
471   }
472 
473   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
seekAsset(int asset, long offset, int whence)474   protected final long seekAsset(int asset, long offset, int whence) {
475     return seekAsset((long) asset, offset, whence);
476   }
477 
478   @HiddenApi @Implementation(minSdk = LOLLIPOP)
seekAsset(long asset, long offset, int whence)479   protected final long seekAsset(long asset, long offset, int whence) {
480     Asset a = getAsset(asset);
481     return a.seek(offset, whence < 0 ? SEEK_SET : SEEK_CUR);
482   }
483 
484   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
getAssetLength(int asset)485   protected final long getAssetLength(int asset) {
486     return getAssetLength((long) asset);
487   }
488 
489   @HiddenApi @Implementation(minSdk = LOLLIPOP)
getAssetLength(long asset)490   protected final long getAssetLength(long asset) {
491     Asset a = getAsset(asset);
492     return a.getLength();
493   }
494 
495   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
getAssetRemainingLength(int asset)496   protected final long getAssetRemainingLength(int asset) {
497     return getAssetRemainingLength((long) asset);
498   }
499 
500   @HiddenApi @Implementation(minSdk = LOLLIPOP)
getAssetRemainingLength(long assetHandle)501   protected final long getAssetRemainingLength(long assetHandle) {
502     Asset a = getAsset(assetHandle);
503 
504     if (a == null) {
505       throw new NullPointerException("asset");
506     }
507 
508     return a.getRemainingLength();
509   }
510 
getAsset(long asset)511   private Asset getAsset(long asset) {
512     return Registries.NATIVE_ASSET_REGISTRY.getNativeObject(asset);
513   }
514 
515   @HiddenApi @Implementation
loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve)516   protected int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve) {
517     if (outValue == null) {
518       throw new NullPointerException("outValue");
519       //return 0;
520     }
521     CppAssetManager am = assetManagerForJavaObject();
522     if (am == null) {
523       return 0;
524     }
525     final ResTable res = am.getResources();
526 
527     final Ref<Res_value> value = new Ref<>(null);
528     final Ref<ResTable_config> config = new Ref<>(null);
529     final Ref<Integer> typeSpecFlags = new Ref<>(null);
530     int block = res.getResource(ident, value, false, density, typeSpecFlags, config);
531     if (kThrowOnBadId) {
532         if (block == BAD_INDEX) {
533             throw new IllegalStateException("Bad resource!");
534             //return 0;
535         }
536     }
537     final Ref<Integer> ref = new Ref<>(ident);
538     if (resolve) {
539         block = res.resolveReference(value, block, ref, typeSpecFlags, config);
540         if (kThrowOnBadId) {
541             if (block == BAD_INDEX) {
542               throw new IllegalStateException("Bad resource!");
543                 //return 0;
544             }
545         }
546     }
547     if (block >= 0) {
548         //return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
549       return copyValue(outValue, res, value.get(), ref.get(), block, typeSpecFlags.get(),
550           config.get());
551 
552     }
553     return block;
554 }
555 
copyValue(TypedValue outValue, ResTable table, Res_value value, int ref, int block, int typeSpecFlags)556   private static int copyValue(TypedValue outValue, ResTable table,  Res_value value, int ref, int block,
557       int typeSpecFlags) {
558     return copyValue(outValue, table, value, ref, block, typeSpecFlags, null);
559   }
560 
copyValue(TypedValue outValue, ResTable table, Res_value value, int ref, int block, int typeSpecFlags, ResTable_config config)561   private static int copyValue(TypedValue outValue, ResTable table,  Res_value value, int ref, int block,
562       int typeSpecFlags, ResTable_config config) {
563     outValue.type = value.dataType;
564     outValue.assetCookie = table.getTableCookie(block);
565     outValue.data = value.data;
566     outValue.string = null;
567     outValue.resourceId = ref;
568     outValue.changingConfigurations = typeSpecFlags;
569 
570     if (config != null) {
571       outValue.density = config.density;
572     }
573     return block;
574   }
575 
getResourceBagValues(int ident, ResTable res)576   public static Map<String, Integer> getResourceBagValues(int ident, ResTable res) {
577     // Now lock down the resource object and start pulling stuff from it.
578     res.lock();
579 
580     HashMap<String, Integer> map;
581     try {
582       final Ref<bag_entry[]> entryRef = new Ref<>(null);
583       final Ref<Integer> typeSpecFlags = new Ref<>(0);
584       int entryCount = res.getBagLocked(ident, entryRef, typeSpecFlags);
585 
586       map = new HashMap<>();
587       bag_entry[] bag_entries = entryRef.get();
588       for (int i=0; i < entryCount; i++) {
589         bag_entry entry = bag_entries[i];
590         ResourceName resourceName = new ResourceName();
591         if (res.getResourceName(entry.map.name.ident, true, resourceName)) {
592           map.put(resourceName.name, entry.map.value.data);
593         }
594       }
595     } finally {
596       res.unlock();
597     }
598 
599     return map;
600   }
601 
602   /**
603    * Returns true if the resource was found, filling in mRetStringBlock and
604    * mRetData.
605    */
606   @Implementation @HiddenApi
loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, boolean resolve)607   protected final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue,
608       boolean resolve) {
609     CppAssetManager am = assetManagerForJavaObject();
610     if (am == null) {
611       return 0;
612     }
613     final ResTable res = am.getResources();
614     return loadResourceBagValueInternal(ident, bagEntryId, outValue, resolve, res);
615   }
616 
getResourceBagValue(int ident, int bagEntryId, ResTable resTable)617   public static String getResourceBagValue(int ident, int bagEntryId, ResTable resTable) {
618     TypedValue outValue = new TypedValue();
619     int blockId = ShadowArscAssetManager
620         .loadResourceBagValueInternal(ident, bagEntryId, outValue, true, resTable);
621     if (outValue.type == TypedValue.TYPE_STRING) {
622       return resTable.getTableStringBlock(blockId).stringAt(outValue.data);
623     } else {
624       return outValue.coerceToString().toString();
625     }
626   }
627 
loadResourceBagValueInternal(int ident, int bagEntryId, TypedValue outValue, boolean resolve, ResTable res)628   private static int loadResourceBagValueInternal(int ident, int bagEntryId, TypedValue outValue,
629       boolean resolve, ResTable res) {
630     // Now lock down the resource object and start pulling stuff from it.
631     res.lock();
632 
633     int block = -1;
634     final Ref<Res_value> valueRef = new Ref<>(null);
635     final Ref<bag_entry[]> entryRef = new Ref<>(null);
636     final Ref<Integer> typeSpecFlags = new Ref<>(0);
637     int entryCount = res.getBagLocked(ident, entryRef, typeSpecFlags);
638 
639     bag_entry[] bag_entries = entryRef.get();
640     for (int i=0; i < entryCount; i++) {
641       bag_entry entry = bag_entries[i];
642       if (bagEntryId == entry.map.name.ident) {
643         block = entry.stringBlock;
644         valueRef.set(entry.map.value);
645       }
646     }
647 
648     res.unlock();
649 
650     if (block < 0) {
651       return block;
652     }
653 
654     final Ref<Integer> ref = new Ref<>(ident);
655     if (resolve) {
656       block = res.resolveReference(valueRef, block, ref, typeSpecFlags);
657       if (kThrowOnBadId) {
658         if (block == BAD_INDEX) {
659           throw new IllegalStateException("Bad resource!");
660         }
661       }
662     }
663     if (block >= 0) {
664       return copyValue(outValue, res, valueRef.get(), ref.get(), block, typeSpecFlags.get());
665     }
666 
667     return block;
668   }
669 
670   // /*package*/ static final int STYLE_NUM_ENTRIES = 6;
671   // /*package*/ static final int STYLE_TYPE = 0;
672   // /*package*/ static final int STYLE_DATA = 1;
673   // /*package*/ static final int STYLE_ASSET_COOKIE = 2;
674   // /*package*/ static final int STYLE_RESOURCE_ID = 3;
675   //
676   // /* Offset within typed data array for native changingConfigurations. */
677   // static final int STYLE_CHANGING_CONFIGURATIONS = 4;
678 
679   // /*package*/ static final int STYLE_DENSITY = 5;
680 
681 /* lowercase hexadecimal notation.  */
682 //# define PRIx8		"x"
683 //      # define PRIx16		"x"
684 //      # define PRIx32		"x"
685 
686   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
applyStyle(int themeToken, int defStyleAttr, int defStyleRes, int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)687   protected static void applyStyle(int themeToken, int defStyleAttr, int defStyleRes,
688       int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
689     applyStyle((long)themeToken, defStyleAttr, defStyleRes, (long)xmlParserToken, attrs,
690         outValues, outIndices);
691   }
692 
693   @HiddenApi @Implementation(minSdk = O, maxSdk = O_MR1)
applyStyle(long themeToken, int defStyleAttr, int defStyleRes, long xmlParserToken, int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress)694   protected static void applyStyle(long themeToken, int defStyleAttr, int defStyleRes,
695       long xmlParserToken, int[] inAttrs, int length, long outValuesAddress,
696       long outIndicesAddress) {
697     ShadowVMRuntime shadowVMRuntime = Shadow.extract(VMRuntime.getRuntime());
698     int[] outValues = (int[])shadowVMRuntime.getObjectForAddress(outValuesAddress);
699     int[] outIndices = (int[])shadowVMRuntime.getObjectForAddress(outIndicesAddress);
700     applyStyle(themeToken, defStyleAttr, defStyleRes, xmlParserToken, inAttrs,
701         outValues, outIndices);
702   }
703 
704   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
applyStyle(long themeToken, int defStyleAttr, int defStyleRes, long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)705   protected static void applyStyle(long themeToken, int defStyleAttr, int defStyleRes,
706       long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
707     ResTableTheme theme = Registries.NATIVE_THEME_REGISTRY.getNativeObject(themeToken);
708     ResXMLParser xmlParser = xmlParserToken == 0
709         ? null
710         : Registries.NATIVE_RES_XML_PARSERS.getNativeObject(xmlParserToken);
711     AttributeResolution.ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes,
712         attrs, attrs.length, outValues, outIndices);
713   }
714 
715   @Implementation @HiddenApi
resolveAttrs(long themeToken, int defStyleAttr, int defStyleRes, int[] inValues, int[] attrs, int[] outValues, int[] outIndices)716   protected static boolean resolveAttrs(long themeToken,
717       int defStyleAttr, int defStyleRes, int[] inValues,
718       int[] attrs, int[] outValues, int[] outIndices) {
719     if (themeToken == 0) {
720       throw new NullPointerException("theme token");
721     }
722     if (attrs == null) {
723       throw new NullPointerException("attrs");
724     }
725     if (outValues == null) {
726       throw new NullPointerException("out values");
727     }
728 
729     final int NI = attrs.length;
730     final int NV = outValues.length;
731     if (NV < (NI*STYLE_NUM_ENTRIES)) {
732       throw new IndexOutOfBoundsException("out values too small");
733     }
734 
735     int[] src = attrs;
736 //    if (src == null) {
737 //      return JNI_FALSE;
738 //    }
739 
740     int[] srcValues = inValues;
741     final int NSV = srcValues == null ? 0 : inValues.length;
742 
743     int[] baseDest = outValues;
744     int destOffset = 0;
745     if (baseDest == null) {
746       return false;
747     }
748 
749     int[] indices = null;
750     if (outIndices != null) {
751       if (outIndices.length > NI) {
752         indices = outIndices;
753       }
754     }
755 
756     ResTableTheme theme = Registries.NATIVE_THEME_REGISTRY.getNativeObject(themeToken);
757 
758     boolean result = AttributeResolution.ResolveAttrs(theme, defStyleAttr, defStyleRes,
759         srcValues, NSV,
760         src, NI,
761         baseDest,
762         indices);
763 
764     if (indices != null) {
765 //      env.ReleasePrimitiveArrayCritical(outIndices, indices, 0);
766     }
767 //    env.ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
768 //    env.ReleasePrimitiveArrayCritical(inValues, srcValues, 0);
769 //    env.ReleasePrimitiveArrayCritical(attrs, src, 0);
770 
771     return result;
772   }
773 
774   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
retrieveAttributes( int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)775   protected final boolean retrieveAttributes(
776       int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
777     return retrieveAttributes((long)xmlParserToken, attrs, outValues, outIndices);
778   }
779 
780   @HiddenApi @Implementation(minSdk = LOLLIPOP)
retrieveAttributes( long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)781   protected final boolean retrieveAttributes(
782       long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
783     if (xmlParserToken == 0) {
784       throw new NullPointerException("xmlParserToken");
785 //      return JNI_FALSE;
786     }
787     if (attrs == null) {
788       throw new NullPointerException("attrs");
789 //      return JNI_FALSE;
790     }
791     if (outValues == null) {
792       throw new NullPointerException("out values");
793 //      return JNI_FALSE;
794     }
795 
796     CppAssetManager am = assetManagerForJavaObject();
797 //    if (am == null) {
798 //      return JNI_FALSE;
799 //    }
800     ResTable res = am.getResources();
801 //    ResXMLParser xmlParser = (ResXMLParser*)xmlParserToken;
802     ResXMLParser xmlParser = Registries.NATIVE_RES_XML_PARSERS.getNativeObject(xmlParserToken);
803 
804 //    const int NI = env.GetArrayLength(attrs);
805 //    const int NV = env.GetArrayLength(outValues);
806     final int NI = attrs.length;
807     final int NV = outValues.length;
808     if (NV < (NI*STYLE_NUM_ENTRIES)) {
809       throw new IndexOutOfBoundsException("out values too small");
810 //      return JNI_FALSE;
811     }
812 
813 //    int[] src = (int[])env.GetPrimitiveArrayCritical(attrs, 0);
814 //    if (src == null) {
815 //      return JNI_FALSE;
816 //    }
817     int[] src = attrs;
818 
819 //    int[] baseDest = (int[])env.GetPrimitiveArrayCritical(outValues, 0);
820     int[] baseDest = outValues;
821     if (baseDest == null) {
822 //      env.ReleasePrimitiveArrayCritical(attrs, src, 0);
823 //      return JNI_FALSE;
824       return false;
825     }
826 
827     int[] indices = null;
828     if (outIndices != null) {
829       if (outIndices.length > NI) {
830 //        indices = (int[])env.GetPrimitiveArrayCritical(outIndices, 0);
831         indices = outIndices;
832       }
833     }
834     boolean result = AttributeResolution.RetrieveAttributes(res, xmlParser, src, NI, baseDest, indices);
835 
836     if (indices != null) {
837 //      indices[0] = indicesIdx;
838 //      env.ReleasePrimitiveArrayCritical(outIndices, indices, 0);
839     }
840 
841 //    env.ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
842 //    env.ReleasePrimitiveArrayCritical(attrs, src, 0);
843 
844     return result;
845   }
846 
847   @HiddenApi @Implementation
getArraySize(int id)848   protected int getArraySize(int id) {
849     CppAssetManager am = assetManagerForJavaObject();
850     if (am == null) {
851       return 0;
852     }
853     final ResTable res = am.getResources();
854 
855     res.lock();
856     final Ref<bag_entry[]> defStyleEnt = new Ref<>(null);
857     int bagOff = res.getBagLocked(id, defStyleEnt, null);
858     res.unlock();
859 
860     return bagOff;
861 
862   }
863 
864   @Implementation @HiddenApi
retrieveArray(int id, int[] outValues)865   protected int retrieveArray(int id, int[] outValues) {
866     if (outValues == null) {
867       throw new NullPointerException("out values");
868     }
869 
870     CppAssetManager am = assetManagerForJavaObject();
871     if (am == null) {
872       return 0 /*JNI_FALSE */;
873     }
874     ResTable res = am.getResources();
875     final Ref<ResTable_config> config = new Ref<>(new ResTable_config());
876     Res_value value;
877     int block;
878 
879     int NV = outValues.length;
880 
881 //    int[] baseDest = (int[])env->GetPrimitiveArrayCritical(outValues, 0);
882     int[] baseDest = outValues;
883     int[] dest = baseDest;
884 //    if (dest == null) {
885 //      throw new NullPointerException(env, "java/lang/OutOfMemoryError", "");
886 //      return JNI_FALSE;
887 //    }
888 
889     // Now lock down the resource object and start pulling stuff from it.
890     res.lock();
891 
892     final Ref<bag_entry[]> arrayEnt = new Ref<>(null);
893     final Ref<Integer> arrayTypeSetFlags = new Ref<>(0);
894     int bagOff = res.getBagLocked(id, arrayEnt, arrayTypeSetFlags);
895 //    final ResTable::bag_entry* endArrayEnt = arrayEnt +
896 //        (bagOff >= 0 ? bagOff : 0);
897 
898     int destOffset = 0;
899     final Ref<Integer> typeSetFlags = new Ref<>(0);
900     while (destOffset < NV && destOffset < bagOff * STYLE_NUM_ENTRIES /*&& arrayEnt < endArrayEnt*/) {
901       bag_entry curArrayEnt = arrayEnt.get()[destOffset / STYLE_NUM_ENTRIES];
902 
903       block = curArrayEnt.stringBlock;
904       typeSetFlags.set(arrayTypeSetFlags.get());
905       config.get().density = 0;
906       value = curArrayEnt.map.value;
907 
908       final Ref<Integer> resid = new Ref<>(0);
909       if (value.dataType != DataType.NULL.code()) {
910         // Take care of resolving the found resource to its final value.
911         //printf("Resolving attribute reference\n");
912         final Ref<Res_value> resValueRef = new Ref<>(value);
913         int newBlock = res.resolveReference(resValueRef, block, resid,
914                     typeSetFlags, config);
915         value = resValueRef.get();
916         if (kThrowOnBadId) {
917           if (newBlock == BAD_INDEX) {
918             throw new IllegalStateException("Bad resource!");
919           }
920         }
921         if (newBlock >= 0) block = newBlock;
922       }
923 
924       // Deal with the special @null value -- it turns back to TYPE_NULL.
925       if (value.dataType == DataType.REFERENCE.code() && value.data == 0) {
926         value = Res_value.NULL_VALUE;
927       }
928 
929       //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
930 
931       // Write the final value back to Java.
932       dest[destOffset + STYLE_TYPE] = value.dataType;
933       dest[destOffset + STYLE_DATA] = value.data;
934       dest[destOffset + STYLE_ASSET_COOKIE] = res.getTableCookie(block);
935       dest[destOffset + STYLE_RESOURCE_ID] = resid.get();
936       dest[destOffset + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags.get();
937       dest[destOffset + STYLE_DENSITY] = config.get().density;
938 //      dest += STYLE_NUM_ENTRIES;
939       destOffset+= STYLE_NUM_ENTRIES;
940 //      arrayEnt++;
941     }
942 
943     destOffset /= STYLE_NUM_ENTRIES;
944 
945     res.unlock();
946 
947 //    env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
948 
949     return destOffset;
950 
951   }
952 
953   @HiddenApi @Implementation
getNativeStringBlock(int block)954   protected Number getNativeStringBlock(int block) {
955     CppAssetManager am = assetManagerForJavaObject();
956     if (am == null) {
957       return RuntimeEnvironment.castNativePtr(0);
958     }
959 
960     return RuntimeEnvironment.castNativePtr(
961         am.getResources().getTableStringBlock(block).getNativePtr());
962   }
963 
964   @Implementation
getAssignedPackageIdentifiers()965   protected final SparseArray<String> getAssignedPackageIdentifiers() {
966     CppAssetManager am = assetManagerForJavaObject();
967     final ResTable res = am.getResources();
968 
969     SparseArray<String> sparseArray = new SparseArray<>();
970     final int N = res.getBasePackageCount();
971     for (int i = 0; i < N; i++) {
972       final String name = res.getBasePackageName(i);
973       sparseArray.put(res.getBasePackageId(i), name);
974     }
975     return sparseArray;
976   }
977 
978   @HiddenApi @Implementation
newTheme()979   protected final Number newTheme() {
980     CppAssetManager am = assetManagerForJavaObject();
981     if (am == null) {
982       return RuntimeEnvironment.castNativePtr(0);
983     }
984     ResTableTheme theme = new ResTableTheme(am.getResources());
985     return RuntimeEnvironment.castNativePtr(Registries.NATIVE_THEME_REGISTRY.register(theme));
986   }
987 
988   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
deleteTheme(int theme)989   protected final void deleteTheme(int theme) {
990     deleteTheme((long) theme);
991   }
992 
993   @HiddenApi @Implementation(minSdk = LOLLIPOP)
deleteTheme(long theme)994   protected final void deleteTheme(long theme) {
995     Registries.NATIVE_THEME_REGISTRY.unregister(theme);
996   }
997 
998   @HiddenApi
999   @Implementation(maxSdk = KITKAT_WATCH)
applyThemeStyle(int themePtr, int styleRes, boolean force)1000   public static void applyThemeStyle(int themePtr, int styleRes, boolean force) {
1001     applyThemeStyle((long)themePtr, styleRes, force);
1002   }
1003 
1004   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
applyThemeStyle(long themePtr, int styleRes, boolean force)1005   public static void applyThemeStyle(long themePtr, int styleRes, boolean force) {
1006     Registries.NATIVE_THEME_REGISTRY.getNativeObject(themePtr).applyStyle(styleRes, force);
1007   }
1008 
1009   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
copyTheme(int destPtr, int sourcePtr)1010   public static void copyTheme(int destPtr, int sourcePtr) {
1011     copyTheme((long) destPtr, (long) sourcePtr);
1012   }
1013 
1014   @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
copyTheme(long destPtr, long sourcePtr)1015   public static void copyTheme(long destPtr, long sourcePtr) {
1016     ResTableTheme dest = Registries.NATIVE_THEME_REGISTRY.getNativeObject(destPtr);
1017     ResTableTheme src = Registries.NATIVE_THEME_REGISTRY.getNativeObject(sourcePtr);
1018     dest.setTo(src);
1019   }
1020 
1021   @HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
loadThemeAttributeValue(int themeHandle, int ident, TypedValue outValue, boolean resolve)1022   protected static int loadThemeAttributeValue(int themeHandle, int ident,
1023       TypedValue outValue, boolean resolve) {
1024     return loadThemeAttributeValue((long) themeHandle, ident, outValue, resolve);
1025   }
1026 
1027   @HiddenApi @Implementation(minSdk = LOLLIPOP)
loadThemeAttributeValue(long themeHandle, int ident, TypedValue outValue, boolean resolve)1028   protected static int loadThemeAttributeValue(long themeHandle, int ident,
1029       TypedValue outValue, boolean resolve) {
1030     ResTableTheme theme = Preconditions.checkNotNull(Registries.NATIVE_THEME_REGISTRY.getNativeObject(themeHandle));
1031     ResTable res = theme.getResTable();
1032 
1033     final Ref<Res_value> value = new Ref<>(null);
1034     // XXX value could be different in different configs!
1035     final Ref<Integer> typeSpecFlags = new Ref<>(0);
1036     int block = theme.GetAttribute(ident, value, typeSpecFlags);
1037     final Ref<Integer> ref = new Ref<>(0);
1038     if (resolve) {
1039       block = res.resolveReference(value, block, ref, typeSpecFlags);
1040       if (kThrowOnBadId) {
1041         if (block == BAD_INDEX) {
1042           throw new IllegalStateException("Bad resource!");
1043         }
1044       }
1045     }
1046     return block >= 0 ? copyValue(outValue, res, value.get(), ref.get(), block, typeSpecFlags.get(), null) : block;
1047   }
1048 
1049 //  /*package*/@HiddenApi @Implementation public static final @NativeConfig
1050 //  int getThemeChangingConfigurations(long theme);
1051 
1052   @HiddenApi @Implementation
openXmlAssetNative(int cookie, String fileName)1053   protected final Number openXmlAssetNative(int cookie, String fileName) throws FileNotFoundException {
1054     CppAssetManager am = assetManagerForJavaObject();
1055     if (am == null) {
1056       return RuntimeEnvironment.castNativePtr(0);
1057     }
1058 
1059     ALOGV("openXmlAsset in %s (Java object %s)\n", am, ShadowArscAssetManager.class);
1060 
1061     String fileName8 = fileName;
1062     if (fileName8 == null) {
1063       return RuntimeEnvironment.castNativePtr(0);
1064     }
1065 
1066     int assetCookie = cookie;
1067     Asset a;
1068     if (isTruthy(assetCookie)) {
1069       a = am.openNonAsset(assetCookie, fileName8, AccessMode.ACCESS_BUFFER);
1070     } else {
1071       final Ref<Integer> assetCookieRef = new Ref<>(assetCookie);
1072       a = am.openNonAsset(fileName8, AccessMode.ACCESS_BUFFER, assetCookieRef);
1073       assetCookie = assetCookieRef.get();
1074     }
1075 
1076     if (a == null) {
1077       throw new FileNotFoundException(fileName8);
1078     }
1079 
1080     final DynamicRefTable dynamicRefTable =
1081         am.getResources().getDynamicRefTableForCookie(assetCookie);
1082     ResXMLTree block = new ResXMLTree(dynamicRefTable);
1083     int err = block.setTo(a.getBuffer(true), (int) a.getLength(), true);
1084     a.close();
1085 //    delete a;
1086 
1087     if (err != NO_ERROR) {
1088       throw new FileNotFoundException("Corrupt XML binary file");
1089     }
1090 
1091     return RuntimeEnvironment.castNativePtr(
1092         Registries.NATIVE_RES_XML_TREES.register(block));
1093   }
1094 
1095   @HiddenApi @Implementation
getArrayStringResource(int arrayResId)1096   protected final String[] getArrayStringResource(int arrayResId) {
1097     CppAssetManager am = assetManagerForJavaObject();
1098     if (am == null) {
1099       return null;
1100     }
1101     final ResTable res = am.getResources();
1102 
1103     final Ref<bag_entry[]> startOfBag = new Ref<>(null);
1104     final int N = res.lockBag(arrayResId, startOfBag);
1105     if (N < 0) {
1106       return null;
1107     }
1108 
1109     String[] array = new String[N];
1110 
1111     final Ref<Res_value> valueRef = new Ref<>(null);
1112     final bag_entry[] bag = startOfBag.get();
1113     int strLen = 0;
1114     for (int i=0; ((int)i)<N; i++) {
1115       valueRef.set(bag[i].map.value);
1116       String str = null;
1117 
1118       // Take care of resolving the found resource to its final value.
1119       int block = res.resolveReference(valueRef, bag[i].stringBlock, null);
1120       if (kThrowOnBadId) {
1121         if (block == BAD_INDEX) {
1122           throw new IllegalStateException("Bad resource!");
1123         }
1124       }
1125       if (valueRef.get().dataType == DataType.STRING.code()) {
1126             final ResStringPool pool = res.getTableStringBlock(block);
1127             str = pool.stringAt(valueRef.get().data);
1128 
1129             // assume we can skip utf8 vs utf 16 handling
1130 
1131 //            final char* str8 = pool.string8At(value.data, &strLen);
1132 //        if (str8 != NULL) {
1133 //          str = env.NewStringUTF(str8);
1134 //        } else {
1135 //                final char16_t* str16 = pool.stringAt(value.data, &strLen);
1136 //          str = env.NewString(reinterpret_cast<final jchar*>(str16),
1137 //              strLen);
1138 //        }
1139 //
1140 //        // If one of our NewString{UTF} calls failed due to memory, an
1141 //        // exception will be pending.
1142 //        if (env.ExceptionCheck()) {
1143 //          res.unlockBag(startOfBag);
1144 //          return NULL;
1145 //        }
1146         if (str == null) {
1147           res.unlockBag(startOfBag);
1148           return null;
1149         }
1150 
1151         array[i] = str;
1152 
1153         // str is not NULL at that point, otherwise ExceptionCheck would have been true.
1154         // If we have a large amount of strings in our array, we might
1155         // overflow the local reference table of the VM.
1156         // env.DeleteLocalRef(str);
1157       }
1158     }
1159     res.unlockBag(startOfBag);
1160     return array;
1161   }
1162 
1163   @HiddenApi @Implementation
getArrayStringInfo(int arrayResId)1164   protected final int[] getArrayStringInfo(int arrayResId) {
1165     CppAssetManager am = assetManagerForJavaObject();
1166     ResTable res = am.getResources();
1167 
1168     final Ref<bag_entry[]> startOfBag = new Ref<>(null);
1169     final int N = res.lockBag(arrayResId, startOfBag);
1170     if (N < 0) {
1171       return null;
1172     }
1173 
1174     int[] array = new int[N * 2];
1175 
1176     final Ref<Res_value> value = new Ref<>(null);
1177     bag_entry[] bag = startOfBag.get();
1178     for (int i = 0, j = 0; i<N; i++) {
1179       int stringIndex = -1;
1180       int stringBlock = 0;
1181       value.set(bag[i].map.value);
1182 
1183       // Take care of resolving the found resource to its final value.
1184       stringBlock = res.resolveReference(value, bag[i].stringBlock, null);
1185       if (value.get().dataType == DataType.STRING.code()) {
1186         stringIndex = value.get().data;
1187       }
1188 
1189       if (kThrowOnBadId) {
1190         if (stringBlock == BAD_INDEX) {
1191           throw new IllegalStateException("Bad resource!");
1192         }
1193       }
1194 
1195       //todo: It might be faster to allocate a C array to contain
1196       //      the blocknums and indices, put them in there and then
1197       //      do just one SetIntArrayRegion()
1198       //env->SetIntArrayRegion(array, j, 1, &stringBlock);
1199       array[j] = stringBlock;
1200       //env->SetIntArrayRegion(array, j + 1, 1, &stringIndex);
1201       array[j+1] = stringIndex;
1202       j += 2;
1203     }
1204     res.unlockBag(startOfBag);
1205     return array;
1206   }
1207 
1208   @HiddenApi @Implementation
getArrayIntResource(int arrayResId)1209   public int[] getArrayIntResource(int arrayResId) {
1210     CppAssetManager am = assetManagerForJavaObject();
1211     if (am == null) {
1212       return null;
1213     }
1214     final ResTable res = am.getResources();
1215 
1216 //    final ResTable::bag_entry* startOfBag;
1217     final Ref<bag_entry[]> startOfBag = new Ref<>(null);
1218     final int N = res.lockBag(arrayResId, startOfBag);
1219     if (N < 0) {
1220       return null;
1221     }
1222 
1223     int[] array = new int[N];
1224     if (array == null) {
1225       res.unlockBag(startOfBag);
1226       return null;
1227     }
1228 
1229     final Ref<Res_value> valueRef = new Ref<>(null);
1230     bag_entry[] bag = startOfBag.get();
1231     for (int i=0; i<N; i++) {
1232       valueRef.set(bag[i].map.value);
1233 
1234       // Take care of resolving the found resource to its final value.
1235       int block = res.resolveReference(valueRef, bag[i].stringBlock, null, null, null);
1236       if (kThrowOnBadId) {
1237         if (block == BAD_INDEX) {
1238           res.unlockBag(startOfBag); // seems like this is missing from android_util_AssetManager.cpp?
1239           throw new IllegalStateException("Bad resource!");
1240 //          return array;
1241         }
1242       }
1243       Res_value value = valueRef.get();
1244       if (value.dataType >= DataType.TYPE_FIRST_INT
1245           && value.dataType <= DataType.TYPE_LAST_INT) {
1246         int intVal = value.data;
1247 //        env->SetIntArrayRegion(array, i, 1, &intVal);
1248         array[i] = intVal;
1249       }
1250     }
1251     res.unlockBag(startOfBag);
1252     return array;
1253   }
1254 
1255   @HiddenApi @Implementation(maxSdk = VERSION_CODES.KITKAT)
init()1256   protected void init() {
1257   //  if (isSystem) {
1258   //    verifySystemIdmaps();
1259   //  }
1260     init(false);
1261   }
1262 
1263   private static CppAssetManager systemCppAssetManager;
1264 
1265   @HiddenApi @Implementation(minSdk = VERSION_CODES.KITKAT_WATCH)
init(boolean isSystem)1266   protected void init(boolean isSystem) {
1267     //  if (isSystem) {
1268     //    verifySystemIdmaps();
1269     //  }
1270 
1271     String androidFrameworkJarPath = RuntimeEnvironment.getAndroidFrameworkJarPath();
1272     Preconditions.checkNotNull(androidFrameworkJarPath);
1273 
1274     if (isSystem) {
1275       synchronized (ShadowArscAssetManager.class) {
1276         if (systemCppAssetManager == null) {
1277           systemCppAssetManager = new CppAssetManager();
1278           systemCppAssetManager.addDefaultAssets(androidFrameworkJarPath);
1279         }
1280       }
1281       this.cppAssetManager = systemCppAssetManager;
1282     } else {
1283       this.cppAssetManager = new CppAssetManager();
1284       cppAssetManager.addDefaultAssets(androidFrameworkJarPath);
1285     }
1286 
1287     ALOGV("Created AssetManager %s for Java object %s\n", cppAssetManager,
1288         ShadowArscAssetManager.class);
1289   }
1290 
1291   @VisibleForTesting
getConfiguration()1292   ResTable_config getConfiguration() {
1293     final Ref<ResTable_config> config = new Ref<>(new ResTable_config());
1294     assetManagerForJavaObject().getConfiguration(config);
1295     return config.get();
1296   }
1297 
1298 //  private native final void destroy();
1299 
1300 //  @HiddenApi
1301 //  @Implementation
1302 //  public int addOverlayPathNative(String idmapPath) {
1303 //    if (Strings.isNullOrEmpty(idmapPath)) {
1304 //      return 0;
1305 //    }
1306 //
1307 //    CppAssetManager am = assetManagerForJavaObject();
1308 //    if (am == null) {
1309 //      return 0;
1310 //    }
1311 //    final Ref<Integer> cookie = new Ref<>(null);
1312 //    boolean res = am.addOverlayPath(new String8(idmapPath), cookie);
1313 //    return (res) ? cookie.get() : 0;
1314 //  }
1315 
1316   @HiddenApi @Implementation
getStringBlockCount()1317   protected int getStringBlockCount() {
1318     CppAssetManager am = assetManagerForJavaObject();
1319     if (am == null) {
1320       return 0;
1321     }
1322     return am.getResources().getTableCount();
1323   }
1324 
1325 
assetManagerForJavaObject()1326   synchronized private CppAssetManager assetManagerForJavaObject() {
1327     if (cppAssetManager == null) {
1328       throw new NullPointerException();
1329     }
1330     return cppAssetManager;
1331   }
1332 
returnParcelFileDescriptor(Asset a, long[] outOffsets)1333   static ParcelFileDescriptor returnParcelFileDescriptor(Asset a, long[] outOffsets)
1334       throws FileNotFoundException {
1335     final Ref<Long> startOffset = new Ref<Long>(-1L);
1336     final Ref<Long> length = new Ref<Long>(-1L);;
1337     FileDescriptor fd = a.openFileDescriptor(startOffset, length);
1338 
1339     if (fd == null) {
1340       throw new FileNotFoundException(
1341           "This file can not be opened as a file descriptor; it is probably compressed");
1342     }
1343 
1344     long[] offsets = outOffsets;
1345     if (offsets == null) {
1346       // fd.close();
1347       return null;
1348     }
1349 
1350     offsets[0] = startOffset.get();
1351     offsets[1] = length.get();
1352 
1353     // FileDescriptor fileDesc = jniCreateFileDescriptor(fd);
1354     // if (fileDesc == null) {
1355     // close(fd);
1356     // return null;
1357     // }
1358 
1359     // TODO: consider doing this
1360     // return new ParcelFileDescriptor(fileDesc);
1361     return ParcelFileDescriptor.open(a.getFile(), ParcelFileDescriptor.MODE_READ_ONLY);
1362   }
1363 
1364   @Override
getAllAssetDirs()1365   Collection<FsFile> getAllAssetDirs() {
1366     ArrayList<FsFile> fsFiles = new ArrayList<>();
1367     for (AssetPath assetPath : cppAssetManager.getAssetPaths()) {
1368       if (assetPath.file.isFile()) {
1369         fsFiles.add(Fs.newJarFile(new File(assetPath.file.getPath())).join("assets"));
1370       } else {
1371         fsFiles.add(assetPath.file);
1372       }
1373     }
1374     return fsFiles;
1375   }
1376 
1377   @Override
getAssetPaths()1378   List<AssetPath> getAssetPaths() {
1379     return assetManagerForJavaObject().getAssetPaths();
1380   }
1381 
1382 }
1383