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