blob: 1a1cf30f8d40b51d1881eded06588e242a83b5e5 [file] [log] [blame]
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static org.robolectric.res.android.Asset.SEEK_CUR;
import static org.robolectric.res.android.Asset.SEEK_SET;
import static org.robolectric.res.android.AttributeResolution.STYLE_ASSET_COOKIE;
import static org.robolectric.res.android.AttributeResolution.STYLE_CHANGING_CONFIGURATIONS;
import static org.robolectric.res.android.AttributeResolution.STYLE_DATA;
import static org.robolectric.res.android.AttributeResolution.STYLE_DENSITY;
import static org.robolectric.res.android.AttributeResolution.STYLE_NUM_ENTRIES;
import static org.robolectric.res.android.AttributeResolution.STYLE_RESOURCE_ID;
import static org.robolectric.res.android.AttributeResolution.STYLE_TYPE;
import static org.robolectric.res.android.AttributeResolution.kThrowOnBadId;
import static org.robolectric.res.android.Errors.BAD_INDEX;
import static org.robolectric.res.android.Errors.NO_ERROR;
import static org.robolectric.res.android.Util.ALOGV;
import static org.robolectric.res.android.Util.isTruthy;
import static org.robolectric.shadow.api.Shadow.directlyOn;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.XmlResourceParser;
import android.os.Build.VERSION_CODES;
import android.os.ParcelFileDescriptor;
import android.util.SparseArray;
import android.util.TypedValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import dalvik.system.VMRuntime;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.res.Fs;
import org.robolectric.res.FsFile;
import org.robolectric.res.android.Asset;
import org.robolectric.res.android.Asset.AccessMode;
import org.robolectric.res.android.AssetDir;
import org.robolectric.res.android.AssetPath;
import org.robolectric.res.android.AttributeResolution;
import org.robolectric.res.android.CppAssetManager;
import org.robolectric.res.android.DataType;
import org.robolectric.res.android.DynamicRefTable;
import org.robolectric.res.android.Ref;
import org.robolectric.res.android.ResStringPool;
import org.robolectric.res.android.ResTable;
import org.robolectric.res.android.ResTable.ResourceName;
import org.robolectric.res.android.ResTable.bag_entry;
import org.robolectric.res.android.ResTableTheme;
import org.robolectric.res.android.ResTable_config;
import org.robolectric.res.android.ResXMLParser;
import org.robolectric.res.android.ResXMLTree;
import org.robolectric.res.android.ResourceTypes.Res_value;
import org.robolectric.res.android.String8;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowAssetManager.Picker;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
// native method impls transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-8.1.0_r22/core/jni/android_util_AssetManager.cpp
@Implements(value = AssetManager.class, maxSdk = VERSION_CODES.O_MR1,
shadowPicker = Picker.class)
public class ShadowArscAssetManager extends ShadowAssetManager {
private static final NativeObjRegistry<ResTableTheme> nativeThemeRegistry = new NativeObjRegistry<>();
private static final NativeObjRegistry<Asset> nativeAssetRegistry = new NativeObjRegistry<>();
@RealObject
protected AssetManager realObject;
private CppAssetManager cppAssetManager;
private ResTable compileTimeResTable;
@Implementation
protected void __constructor__() {
invokeConstructor(AssetManager.class, realObject);
}
@Implementation
protected void __constructor__(boolean isSystem) {
invokeConstructor(AssetManager.class, realObject,
ClassParameter.from(boolean.class, isSystem));
}
@Resetter
public static void reset() {
// todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for resetters...
if (!useLegacy()) {
ReflectionHelpers.setStaticField(AssetManager.class, "sSystem", null);
// nativeThemeRegistry.clear();
// nativeXMLParserRegistry.clear(); // todo: shouldn't these be freed explicitly? [yes! xw]
// nativeAssetRegistry.clear();
}
}
@HiddenApi @Implementation
public CharSequence getResourceText(int ident) {
return directlyOn(realObject, AssetManager.class, "getResourceText",
ClassParameter.from(int.class, ident));
}
@HiddenApi @Implementation
public CharSequence getResourceBagText(int ident, int bagEntryId) {
return directlyOn(realObject, AssetManager.class, "getResourceBagText",
ClassParameter.from(int.class, ident),
ClassParameter.from(int.class, bagEntryId));
}
@HiddenApi @Implementation
public String[] getResourceStringArray(final int id) {
return directlyOn(realObject, AssetManager.class, "getResourceStringArray",
ClassParameter.from(int.class, id));
}
@HiddenApi @Implementation
public boolean getResourceValue(int ident, int density, TypedValue outValue,
boolean resolveRefs) {
return directlyOn(realObject, AssetManager.class, "getResourceValue",
ClassParameter.from(int.class, ident),
ClassParameter.from(int.class, density),
ClassParameter.from(TypedValue.class, outValue),
ClassParameter.from(boolean.class, resolveRefs));
}
@HiddenApi @Implementation
public CharSequence[] getResourceTextArray(int resId) {
return directlyOn(realObject, AssetManager.class, "getResourceTextArray",
ClassParameter.from(int.class, resId));
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
public boolean getThemeValue(int themePtr, int ident, TypedValue outValue, boolean resolveRefs) {
return directlyOn(realObject, AssetManager.class, "getThemeValue",
ClassParameter.from(int.class, themePtr),
ClassParameter.from(int.class, ident),
ClassParameter.from(TypedValue.class, outValue),
ClassParameter.from(boolean.class, resolveRefs));
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
public boolean getThemeValue(long themePtr, int ident, TypedValue outValue, boolean resolveRefs) {
return directlyOn(realObject, AssetManager.class, "getThemeValue",
ClassParameter.from(long.class, themePtr),
ClassParameter.from(int.class, ident),
ClassParameter.from(TypedValue.class, outValue),
ClassParameter.from(boolean.class, resolveRefs));
}
@HiddenApi @Implementation
protected Object ensureStringBlocks() {
return directlyOn(realObject, AssetManager.class, "ensureStringBlocks");
}
@Implementation
public final InputStream open(String fileName) throws IOException {
return directlyOn(realObject, AssetManager.class).open(fileName);
}
@Implementation
public final InputStream open(String fileName, int accessMode) throws IOException {
return directlyOn(realObject, AssetManager.class).open(fileName, accessMode);
}
@Implementation
public final AssetFileDescriptor openFd(String fileName) throws IOException {
try {
return directlyOn(realObject, AssetManager.class).openFd(fileName);
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
throw e;
}
}
}
@Implementation
public final String[] list(String path) throws IOException {
CppAssetManager am = assetManagerForJavaObject();
String fileName8 = path;
if (fileName8 == null) {
return null;
}
AssetDir dir = am.openDir(fileName8);
if (dir == null) {
throw new FileNotFoundException(fileName8);
}
int N = dir.getFileCount();
String[] array = new String[dir.getFileCount()];
for (int i=0; i<N; i++) {
String8 name = dir.getFileName(i);
array[i] = name.string();
}
return array;
}
@HiddenApi @Implementation
public final InputStream openNonAsset(int cookie, String fileName, int accessMode)
throws IOException {
return directlyOn(realObject, AssetManager.class, "openNonAsset",
ClassParameter.from(int.class, cookie),
ClassParameter.from(String.class, fileName),
ClassParameter.from(int.class, accessMode));
}
@HiddenApi @Implementation
public final AssetFileDescriptor openNonAssetFd(int cookie, String fileName) throws IOException {
return directlyOn(realObject, AssetManager.class, "openNonAssetFd",
ClassParameter.from(int.class, cookie),
ClassParameter.from(String.class, fileName));
}
@Implementation
public final XmlResourceParser openXmlResourceParser(int cookie, String fileName)
throws IOException {
return directlyOn(realObject, AssetManager.class).openXmlResourceParser(cookie, fileName);
}
// @HiddenApi @Implementation(minSdk = VERSION_CODES.P)
// public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) {
// throw new UnsupportedOperationException("implement me");
// }
@HiddenApi @Implementation
public int addAssetPath(String path) {
if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.JELLY_BEAN_MR1) {
return addAssetPathNative(path);
} else {
return directlyOn(realObject, AssetManager.class, "addAssetPath",
ClassParameter.from(String.class, path));
}
}
@HiddenApi @Implementation
public boolean isUpToDate() {
return directlyOn(realObject, AssetManager.class, "isUpToDate");
}
@HiddenApi @Implementation
public void setLocale(String locale) {
directlyOn(realObject, AssetManager.class, "setLocale",
ClassParameter.from(String.class, locale));
}
@Implementation
public String[] getLocales() {
return directlyOn(realObject, AssetManager.class).getLocales();
}
@HiddenApi @Implementation(maxSdk = N_MR1)
final public void 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) {
setConfiguration(mcc, mnc, locale,
orientation, touchscreen, density, keyboard,
keyboardHidden, navigation, screenWidth, screenHeight,
smallestScreenWidthDp, screenWidthDp, screenHeightDp,
screenLayout, uiMode, 0, sdkVersion);
}
@HiddenApi @Implementation(minSdk = O)
public void 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) {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return;
}
ResTable_config config = new ResTable_config();
// memset(&config, 0, sizeof(config));
// const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;
// Constants duplicated from Java class android.content.res.Configuration.
int kScreenLayoutRoundMask = 0x300;
int kScreenLayoutRoundShift = 8;
config.mcc = mcc;
config.mnc = mnc;
config.orientation = orientation;
config.touchscreen = touchscreen;
config.density = density;
config.keyboard = keyboard;
config.inputFlags = keyboardHidden;
config.navigation = navigation;
config.screenWidth = screenWidth;
config.screenHeight = screenHeight;
config.smallestScreenWidthDp = smallestScreenWidthDp;
config.screenWidthDp = screenWidthDp;
config.screenHeightDp = screenHeightDp;
config.screenLayout = screenLayout;
config.uiMode = uiMode;
config.colorMode = (byte) colorMode;
config.sdkVersion = sdkVersion;
config.minorVersion = 0;
// In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
// in C++. We must extract the round qualifier out of the Java screenLayout and put it
// into screenLayout2.
config.screenLayout2 =
(byte) ((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
am.setConfiguration(config, locale);
// if (locale != null) env->ReleaseStringUTFChars(locale, locale8);
}
@HiddenApi @Implementation
public Number createTheme() {
return directlyOn(realObject, AssetManager.class, "createTheme");
}
@HiddenApi @Implementation
protected static void dumpTheme(long theme, int priority, String tag, String prefix) {
throw new UnsupportedOperationException("not yet implemented");
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
public void releaseTheme(int themePtr) {
directlyOn(realObject, AssetManager.class, "releaseTheme",
ClassParameter.from(int.class, themePtr));
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
public void releaseTheme(long themePtr) {
directlyOn(realObject, AssetManager.class, "releaseTheme",
ClassParameter.from(long.class, themePtr));
}
@Implementation
public String getResourceName(int resid) {
CppAssetManager am = assetManagerForJavaObject();
ResourceName name = new ResourceName();
if (!am.getResources().getResourceName(resid, true, name)) {
return null;
}
StringBuilder str = new StringBuilder();
if (name.packageName != null) {
str.append(name.packageName.trim());
}
if (name.type != null) {
if (str.length() > 0) {
char div = ':';
str.append(div);
}
str.append(name.type);
}
if (name.name != null) {
if (str.length() > 0) {
char div = '/';
str.append(div);
}
str.append(name.name);
}
return str.toString();
}
@Implementation
public String getResourcePackageName(int resid) {
CppAssetManager cppAssetManager = assetManagerForJavaObject();
ResourceName name = new ResourceName();
if (!cppAssetManager.getResources().getResourceName(resid, true, name)) {
return null;
}
return name.packageName.trim();
}
@Implementation
public String getResourceTypeName(int resid) {
CppAssetManager cppAssetManager = assetManagerForJavaObject();
ResourceName name = new ResourceName();
if (!cppAssetManager.getResources().getResourceName(resid, true, name)) {
return null;
}
return name.type;
}
@Implementation
public String getResourceEntryName(int resid) {
CppAssetManager cppAssetManager = assetManagerForJavaObject();
ResourceName name = new ResourceName();
if (!cppAssetManager.getResources().getResourceName(resid, true, name)) {
return null;
}
return name.name;
}
//////////// native method implementations
// public native final String[] list(String path)
// throws IOException;
@HiddenApi @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M)
final protected int addAssetPathNative(String path) {
return addAssetPathNative(path, false);
}
@HiddenApi @Implementation(minSdk = VERSION_CODES.N)
protected int addAssetPathNative(String path, boolean appAsLib) {
if (Strings.isNullOrEmpty(path)) {
return 0;
}
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return 0;
}
Ref<Integer> cookie = new Ref<>(null);
boolean res = am.addAssetPath(new String8(path), cookie, appAsLib);
return (res) ? cookie.get() : 0;
}
@HiddenApi @Implementation
public int getResourceIdentifier(String name, String defType, String defPackage) {
if (Strings.isNullOrEmpty(name)) {
return 0;
}
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return 0;
}
int ident = am.getResources().identifierForName(name, defType, defPackage);
return ident;
}
@HiddenApi @Implementation
protected final Number openAsset(String fileName, int mode) throws FileNotFoundException {
CppAssetManager am = assetManagerForJavaObject();
ALOGV("openAsset in %s", am);
String fileName8 = fileName;
if (fileName8 == null) {
throw new IllegalArgumentException("Empty file name");
}
if (mode != AccessMode.ACCESS_UNKNOWN.mode() && mode != AccessMode.ACCESS_RANDOM.mode()
&& mode != AccessMode.ACCESS_STREAMING.mode() && mode != AccessMode.ACCESS_BUFFER.mode()) {
throw new IllegalArgumentException("Bad access mode");
}
Asset a = am.open(fileName8, AccessMode.fromInt(mode));
if (a == null) {
throw new FileNotFoundException(fileName8);
}
//printf("Created Asset Stream: %p\n", a);
return RuntimeEnvironment.castNativePtr(nativeAssetRegistry.getNativeObjectId(a));
}
@HiddenApi @Implementation
protected ParcelFileDescriptor openAssetFd(String fileName, long[] outOffsets) throws IOException {
CppAssetManager am = assetManagerForJavaObject();
ALOGV("openAssetFd in %s", am);
String fileName8 = fileName;
if (fileName8 == null) {
return null;
}
Asset a = am.open(fileName8, Asset.AccessMode.ACCESS_RANDOM);
if (a == null) {
throw new FileNotFoundException(fileName8);
}
return returnParcelFileDescriptor(a, outOffsets);
}
@HiddenApi @Implementation
protected final Number openNonAssetNative(int cookie, String fileName,
int accessMode) throws FileNotFoundException {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return RuntimeEnvironment.castNativePtr(0);
}
ALOGV("openNonAssetNative in %s (Java object %s)\n", am, AssetManager.class);
String fileName8 = fileName;
if (fileName8 == null) {
return RuntimeEnvironment.castNativePtr(-1);
}
AccessMode mode = AccessMode.fromInt(accessMode);
if (mode != Asset.AccessMode.ACCESS_UNKNOWN && mode != Asset.AccessMode.ACCESS_RANDOM
&& mode != Asset.AccessMode.ACCESS_STREAMING && mode != Asset.AccessMode.ACCESS_BUFFER) {
throw new IllegalArgumentException("Bad access mode");
}
Asset a = isTruthy(cookie)
? am.openNonAsset(cookie, fileName8, mode)
: am.openNonAsset(fileName8, mode, null);
if (a == null) {
throw new FileNotFoundException(fileName8);
}
long assetId = nativeAssetRegistry.getNativeObjectId(a);
// todo: something better than this [xw]
a.onClose = () -> destroyAsset(assetId);
//printf("Created Asset Stream: %p\n", a);
return RuntimeEnvironment.castNativePtr(assetId);
}
@HiddenApi @Implementation
protected ParcelFileDescriptor openNonAssetFdNative(int cookie,
String fileName, long[] outOffsets) throws IOException {
CppAssetManager am = assetManagerForJavaObject();
ALOGV("openNonAssetFd in %s (Java object %s)", am, this);
if (fileName == null) {
return null;
}
Asset a = isTruthy(cookie)
? am.openNonAsset(cookie, fileName, Asset.AccessMode.ACCESS_RANDOM)
: am.openNonAsset(fileName, Asset.AccessMode.ACCESS_RANDOM, null);
if (a == null) {
throw new FileNotFoundException(fileName);
}
//printf("Created Asset Stream: %p\n", a);
return returnParcelFileDescriptor(a, outOffsets);
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected final void destroyAsset(int asset) {
destroyAsset((long) asset);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
protected final void destroyAsset(long asset) {
nativeAssetRegistry.unregister(asset);
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected final int readAssetChar(int asset) {
return readAssetChar((long) asset);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
protected final int readAssetChar(long asset) {
Asset a = getAsset(asset);
byte[] b = new byte[1];
int res = a.read(b, 1);
return res == 1 ? b[0] & 0xff : -1;
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected final int readAsset(int asset, byte[] b, int off, int len) throws IOException {
return readAsset((long) asset, b, off, len);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
protected final int readAsset(long asset, byte[] bArray, int off, int len) throws IOException {
Asset a = getAsset(asset);
if (a == null || bArray == null) {
throw new NullPointerException("asset");
}
if (len == 0) {
return 0;
}
int bLen = bArray.length;
if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
throw new IndexOutOfBoundsException();
}
byte[] b = bArray;
int res = a.read(b, off, len);
if (res > 0) return res;
if (res < 0) {
throw new IOException();
}
return -1;
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected final long seekAsset(int asset, long offset, int whence) {
return seekAsset((long) asset, offset, whence);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
protected final long seekAsset(long asset, long offset, int whence) {
Asset a = getAsset(asset);
return a.seek(offset, whence < 0 ? SEEK_SET : SEEK_CUR);
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected final long getAssetLength(int asset) {
return getAssetLength((long) asset);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
protected final long getAssetLength(long asset) {
Asset a = getAsset(asset);
return a.getLength();
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected final long getAssetRemainingLength(int asset) {
return getAssetRemainingLength((long) asset);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
protected final long getAssetRemainingLength(long assetHandle) {
Asset a = getAsset(assetHandle);
if (a == null) {
throw new NullPointerException("asset");
}
return a.getRemainingLength();
}
private Asset getAsset(long asset) {
return nativeAssetRegistry.getNativeObject(asset);
}
@HiddenApi @Implementation
protected int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve) {
if (outValue == null) {
throw new NullPointerException("outValue");
//return 0;
}
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return 0;
}
final ResTable res = am.getResources();
Ref<Res_value> value = new Ref<>(null);
Ref<ResTable_config> config = new Ref<>(null);
Ref<Integer> typeSpecFlags = new Ref<>(null);
int block = res.getResource(ident, value, false, density, typeSpecFlags, config);
if (kThrowOnBadId) {
if (block == BAD_INDEX) {
throw new IllegalStateException("Bad resource!");
//return 0;
}
}
Ref<Integer> ref = new Ref<>(ident);
if (resolve) {
block = res.resolveReference(value, block, ref, typeSpecFlags, config);
if (kThrowOnBadId) {
if (block == BAD_INDEX) {
throw new IllegalStateException("Bad resource!");
//return 0;
}
}
}
if (block >= 0) {
//return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
return copyValue(outValue, res, value.get(), ref.get(), block, typeSpecFlags.get(),
config.get());
}
return block;
}
private static int copyValue(TypedValue outValue, ResTable table, Res_value value, int ref, int block,
int typeSpecFlags) {
return copyValue(outValue, table, value, ref, block, typeSpecFlags, null);
}
private static int copyValue(TypedValue outValue, ResTable table, Res_value value, int ref, int block,
int typeSpecFlags, ResTable_config config) {
outValue.type = value.dataType;
outValue.assetCookie = table.getTableCookie(block);
outValue.data = value.data;
outValue.string = null;
outValue.resourceId = ref;
outValue.changingConfigurations = typeSpecFlags;
if (config != null) {
outValue.density = config.density;
}
return block;
}
public static Map<String, Integer> getResourceBagValues(int ident, ResTable res) {
// Now lock down the resource object and start pulling stuff from it.
res.lock();
HashMap<String, Integer> map;
try {
Ref<bag_entry[]> entryRef = new Ref<>(null);
Ref<Integer> typeSpecFlags = new Ref<>(0);
int entryCount = res.getBagLocked(ident, entryRef, typeSpecFlags);
map = new HashMap<>();
bag_entry[] bag_entries = entryRef.get();
for (int i=0; i < entryCount; i++) {
bag_entry entry = bag_entries[i];
ResourceName resourceName = new ResourceName();
if (res.getResourceName(entry.map.name.ident, true, resourceName)) {
map.put(resourceName.name, entry.map.value.data);
}
}
} finally {
res.unlock();
}
return map;
}
/**
* Returns true if the resource was found, filling in mRetStringBlock and
* mRetData.
*/
@Implementation @HiddenApi
protected final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue,
boolean resolve) {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return 0;
}
final ResTable res = am.getResources();
return loadResourceBagValueInternal(ident, bagEntryId, outValue, resolve, res);
}
public static String getResourceBagValue(int ident, int bagEntryId, ResTable resTable) {
TypedValue outValue = new TypedValue();
int blockId = ShadowArscAssetManager
.loadResourceBagValueInternal(ident, bagEntryId, outValue, true, resTable);
if (outValue.type == TypedValue.TYPE_STRING) {
return resTable.getTableStringBlock(blockId).stringAt(outValue.data);
} else {
return outValue.coerceToString().toString();
}
}
private static int loadResourceBagValueInternal(int ident, int bagEntryId, TypedValue outValue,
boolean resolve, ResTable res) {
// Now lock down the resource object and start pulling stuff from it.
res.lock();
int block = -1;
Ref<Res_value> valueRef = new Ref<>(null);
Ref<bag_entry[]> entryRef = new Ref<>(null);
Ref<Integer> typeSpecFlags = new Ref<>(0);
int entryCount = res.getBagLocked(ident, entryRef, typeSpecFlags);
bag_entry[] bag_entries = entryRef.get();
for (int i=0; i < entryCount; i++) {
bag_entry entry = bag_entries[i];
if (bagEntryId == entry.map.name.ident) {
block = entry.stringBlock;
valueRef.set(entry.map.value);
}
}
res.unlock();
if (block < 0) {
return block;
}
Ref<Integer> ref = new Ref<Integer>(ident);
if (resolve) {
block = res.resolveReference(valueRef, block, ref, typeSpecFlags);
if (kThrowOnBadId) {
if (block == BAD_INDEX) {
throw new IllegalStateException("Bad resource!");
}
}
}
if (block >= 0) {
return copyValue(outValue, res, valueRef.get(), ref.get(), block, typeSpecFlags.get());
}
return block;
}
// /*package*/ static final int STYLE_NUM_ENTRIES = 6;
// /*package*/ static final int STYLE_TYPE = 0;
// /*package*/ static final int STYLE_DATA = 1;
// /*package*/ static final int STYLE_ASSET_COOKIE = 2;
// /*package*/ static final int STYLE_RESOURCE_ID = 3;
//
// /* Offset within typed data array for native changingConfigurations. */
// static final int STYLE_CHANGING_CONFIGURATIONS = 4;
// /*package*/ static final int STYLE_DENSITY = 5;
/* lowercase hexadecimal notation. */
//# define PRIx8 "x"
// # define PRIx16 "x"
// # define PRIx32 "x"
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected static void applyStyle(int themeToken, int defStyleAttr, int defStyleRes,
int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
applyStyle((long)themeToken, defStyleAttr, defStyleRes, (long)xmlParserToken, attrs,
outValues, outIndices);
}
@HiddenApi @Implementation(minSdk = O, maxSdk = O_MR1)
protected static void applyStyle(long themeToken, int defStyleAttr, int defStyleRes,
long xmlParserToken, int[] inAttrs, int length, long outValuesAddress,
long outIndicesAddress) {
ShadowVMRuntime shadowVMRuntime = Shadow.extract(VMRuntime.getRuntime());
int[] outValues = (int[])shadowVMRuntime.getObjectForAddress(outValuesAddress);
int[] outIndices = (int[])shadowVMRuntime.getObjectForAddress(outIndicesAddress);
applyStyle(themeToken, defStyleAttr, defStyleRes, xmlParserToken, inAttrs,
outValues, outIndices);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
protected static void applyStyle(long themeToken, int defStyleAttr, int defStyleRes,
long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
ResTableTheme theme = nativeThemeRegistry.getNativeObject(themeToken);
ResXMLParser xmlParser = xmlParserToken == 0
? null
: ShadowXmlBlock.NATIVE_RES_XML_PARSERS.getNativeObject(xmlParserToken);
AttributeResolution.ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes,
attrs, attrs.length, outValues, outIndices);
}
private static int[] dupeLtoI(long[] longs) {
int[] ints = new int[longs.length];
for (int i = 0; i < ints.length; i++) {
ints[i] = (int) longs[i];
}
return ints;
}
private static void copyItoL(int[] ints, long[] longs) {
for (int i = 0; i < ints.length; i++) {
longs[i] = ints[i];
}
}
@Implementation @HiddenApi
protected static boolean resolveAttrs(long themeToken,
int defStyleAttr, int defStyleRes, int[] inValues,
int[] attrs, int[] outValues, int[] outIndices) {
if (themeToken == 0) {
throw new NullPointerException("theme token");
}
if (attrs == null) {
throw new NullPointerException("attrs");
}
if (outValues == null) {
throw new NullPointerException("out values");
}
final int NI = attrs.length;
final int NV = outValues.length;
if (NV < (NI*STYLE_NUM_ENTRIES)) {
throw new IndexOutOfBoundsException("out values too small");
}
int[] src = attrs;
// if (src == null) {
// return JNI_FALSE;
// }
int[] srcValues = inValues;
final int NSV = srcValues == null ? 0 : inValues.length;
int[] baseDest = outValues;
int destOffset = 0;
if (baseDest == null) {
return false;
}
int[] indices = null;
if (outIndices != null) {
if (outIndices.length > NI) {
indices = outIndices;
}
}
ResTableTheme theme = nativeThemeRegistry.getNativeObject(themeToken);
boolean result = AttributeResolution.ResolveAttrs(theme, defStyleAttr, defStyleRes,
srcValues, NSV,
src, NI,
baseDest,
indices);
if (indices != null) {
// env.ReleasePrimitiveArrayCritical(outIndices, indices, 0);
}
// env.ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
// env.ReleasePrimitiveArrayCritical(inValues, srcValues, 0);
// env.ReleasePrimitiveArrayCritical(attrs, src, 0);
return result;
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected final boolean retrieveAttributes(
int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
return retrieveAttributes((long)xmlParserToken, attrs, outValues, outIndices);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
protected final boolean retrieveAttributes(
long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) {
if (xmlParserToken == 0) {
throw new NullPointerException("xmlParserToken");
// return JNI_FALSE;
}
if (attrs == null) {
throw new NullPointerException("attrs");
// return JNI_FALSE;
}
if (outValues == null) {
throw new NullPointerException("out values");
// return JNI_FALSE;
}
CppAssetManager am = assetManagerForJavaObject();
// if (am == null) {
// return JNI_FALSE;
// }
ResTable res = am.getResources();
// ResXMLParser xmlParser = (ResXMLParser*)xmlParserToken;
ResXMLParser xmlParser = ShadowXmlBlock.NATIVE_RES_XML_PARSERS.getNativeObject(xmlParserToken);
// const int NI = env.GetArrayLength(attrs);
// const int NV = env.GetArrayLength(outValues);
final int NI = attrs.length;
final int NV = outValues.length;
if (NV < (NI*STYLE_NUM_ENTRIES)) {
throw new IndexOutOfBoundsException("out values too small");
// return JNI_FALSE;
}
// int[] src = (int[])env.GetPrimitiveArrayCritical(attrs, 0);
// if (src == null) {
// return JNI_FALSE;
// }
int[] src = attrs;
// int[] baseDest = (int[])env.GetPrimitiveArrayCritical(outValues, 0);
int[] baseDest = outValues;
if (baseDest == null) {
// env.ReleasePrimitiveArrayCritical(attrs, src, 0);
// return JNI_FALSE;
return false;
}
int[] indices = null;
if (outIndices != null) {
if (outIndices.length > NI) {
// indices = (int[])env.GetPrimitiveArrayCritical(outIndices, 0);
indices = outIndices;
}
}
boolean result = AttributeResolution.RetrieveAttributes(res, xmlParser, src, NI, baseDest, indices);
if (indices != null) {
// indices[0] = indicesIdx;
// env.ReleasePrimitiveArrayCritical(outIndices, indices, 0);
}
// env.ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
// env.ReleasePrimitiveArrayCritical(attrs, src, 0);
return result;
}
@HiddenApi @Implementation
protected int getArraySize(int id) {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return 0;
}
final ResTable res = am.getResources();
res.lock();
final Ref<bag_entry[]> defStyleEnt = new Ref<>(null);
int bagOff = res.getBagLocked(id, defStyleEnt, null);
res.unlock();
return bagOff;
}
@Implementation @HiddenApi
protected int retrieveArray(int id, int[] outValues) {
if (outValues == null) {
throw new NullPointerException("out values");
}
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return 0 /*JNI_FALSE */;
}
ResTable res = am.getResources();
Ref<ResTable_config> config = new Ref<>(new ResTable_config());
Res_value value;
int block;
int NV = outValues.length;
// int[] baseDest = (int[])env->GetPrimitiveArrayCritical(outValues, 0);
int[] baseDest = outValues;
int[] dest = baseDest;
// if (dest == null) {
// throw new NullPointerException(env, "java/lang/OutOfMemoryError", "");
// return JNI_FALSE;
// }
// Now lock down the resource object and start pulling stuff from it.
res.lock();
Ref<bag_entry[]> arrayEnt = new Ref<>(null);
Ref<Integer> arrayTypeSetFlags = new Ref<>(0);
int bagOff = res.getBagLocked(id, arrayEnt, arrayTypeSetFlags);
// final ResTable::bag_entry* endArrayEnt = arrayEnt +
// (bagOff >= 0 ? bagOff : 0);
int destOffset = 0;
final Ref<Integer> typeSetFlags = new Ref<>(0);
while (destOffset < NV && destOffset < bagOff * STYLE_NUM_ENTRIES /*&& arrayEnt < endArrayEnt*/) {
bag_entry curArrayEnt = arrayEnt.get()[destOffset / STYLE_NUM_ENTRIES];
block = curArrayEnt.stringBlock;
typeSetFlags.set(arrayTypeSetFlags.get());
config.get().density = 0;
value = curArrayEnt.map.value;
final Ref<Integer> resid = new Ref<>(0);
if (value.dataType != DataType.NULL.code()) {
// Take care of resolving the found resource to its final value.
//printf("Resolving attribute reference\n");
Ref<Res_value> resValueRef = new Ref<>(value);
int newBlock = res.resolveReference(resValueRef, block, resid,
typeSetFlags, config);
value = resValueRef.get();
if (kThrowOnBadId) {
if (newBlock == BAD_INDEX) {
throw new IllegalStateException("Bad resource!");
}
}
if (newBlock >= 0) block = newBlock;
}
// Deal with the special @null value -- it turns back to TYPE_NULL.
if (value.dataType == DataType.REFERENCE.code() && value.data == 0) {
value = Res_value.NULL_VALUE;
}
//printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
// Write the final value back to Java.
dest[destOffset + STYLE_TYPE] = value.dataType;
dest[destOffset + STYLE_DATA] = value.data;
dest[destOffset + STYLE_ASSET_COOKIE] = res.getTableCookie(block);
dest[destOffset + STYLE_RESOURCE_ID] = resid.get();
dest[destOffset + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags.get();
dest[destOffset + STYLE_DENSITY] = config.get().density;
// dest += STYLE_NUM_ENTRIES;
destOffset+= STYLE_NUM_ENTRIES;
// arrayEnt++;
}
destOffset /= STYLE_NUM_ENTRIES;
res.unlock();
// env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
return destOffset;
}
@HiddenApi @Implementation
protected Number getNativeStringBlock(int block) {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return RuntimeEnvironment.castNativePtr(0);
}
return RuntimeEnvironment.castNativePtr(
ShadowStringBlock.getNativePointer(am.getResources().getTableStringBlock(block)));
}
@Implementation
public final SparseArray<String> getAssignedPackageIdentifiers() {
CppAssetManager am = assetManagerForJavaObject();
final ResTable res = am.getResources();
SparseArray<String> sparseArray = new SparseArray<>();
final int N = res.getBasePackageCount();
for (int i = 0; i < N; i++) {
final String name = res.getBasePackageName(i);
sparseArray.put(res.getBasePackageId(i), name);
}
return sparseArray;
}
@HiddenApi @Implementation
protected final Number newTheme() {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return RuntimeEnvironment.castNativePtr(0);
}
ResTableTheme theme = new ResTableTheme(am.getResources());
return RuntimeEnvironment.castNativePtr(nativeThemeRegistry.getNativeObjectId(theme));
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected final void deleteTheme(int theme) {
deleteTheme((long) theme);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
protected final void deleteTheme(long theme) {
nativeThemeRegistry.unregister(theme);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void applyThemeStyle(int themePtr, int styleRes, boolean force) {
applyThemeStyle((long)themePtr, styleRes, force);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
public static void applyThemeStyle(long themePtr, int styleRes, boolean force) {
nativeThemeRegistry.getNativeObject(themePtr).applyStyle(styleRes, force);
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
public static void copyTheme(int destPtr, int sourcePtr) {
copyTheme((long) destPtr, (long) sourcePtr);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
public static void copyTheme(long destPtr, long sourcePtr) {
ResTableTheme dest = nativeThemeRegistry.getNativeObject(destPtr);
ResTableTheme src = nativeThemeRegistry.getNativeObject(sourcePtr);
dest.setTo(src);
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
protected static int loadThemeAttributeValue(int themeHandle, int ident,
TypedValue outValue, boolean resolve) {
return loadThemeAttributeValue((long) themeHandle, ident, outValue, resolve);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
protected static int loadThemeAttributeValue(long themeHandle, int ident,
TypedValue outValue, boolean resolve) {
ResTableTheme theme = Preconditions.checkNotNull(nativeThemeRegistry.getNativeObject(themeHandle));
ResTable res = theme.getResTable();
Ref<Res_value> value = new Ref<>(null);
// XXX value could be different in different configs!
Ref<Integer> typeSpecFlags = new Ref<>(0);
int block = theme.getAttribute(ident, value, typeSpecFlags);
Ref<Integer> ref = new Ref<>(0);
if (resolve) {
block = res.resolveReference(value, block, ref, typeSpecFlags);
if (kThrowOnBadId) {
if (block == BAD_INDEX) {
throw new IllegalStateException("Bad resource!");
}
}
}
return block >= 0 ? copyValue(outValue, res, value.get(), ref.get(), block, typeSpecFlags.get(), null) : block;
}
// /*package*/@HiddenApi @Implementation public static final @NativeConfig
// int getThemeChangingConfigurations(long theme);
@HiddenApi @Implementation
protected final Number openXmlAssetNative(int cookie, String fileName) throws FileNotFoundException {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return RuntimeEnvironment.castNativePtr(0);
}
ALOGV("openXmlAsset in %s (Java object %s)\n", am, ShadowArscAssetManager.class);
String fileName8 = fileName;
if (fileName8 == null) {
return RuntimeEnvironment.castNativePtr(0);
}
int assetCookie = cookie;
Asset a;
if (isTruthy(assetCookie)) {
a = am.openNonAsset(assetCookie, fileName8, AccessMode.ACCESS_BUFFER);
} else {
Ref<Integer> assetCookieRef = new Ref<>(assetCookie);
a = am.openNonAsset(fileName8, AccessMode.ACCESS_BUFFER, assetCookieRef);
assetCookie = assetCookieRef.get();
}
if (a == null) {
throw new FileNotFoundException(fileName8);
}
final DynamicRefTable dynamicRefTable =
am.getResources().getDynamicRefTableForCookie(assetCookie);
ResXMLTree block = new ResXMLTree(dynamicRefTable);
int err = block.setTo(a.getBuffer(true), (int) a.getLength(), true);
a.close();
// delete a;
if (err != NO_ERROR) {
throw new FileNotFoundException("Corrupt XML binary file");
}
return RuntimeEnvironment.castNativePtr(
ShadowXmlBlock.NATIVE_RES_XML_TREES.getNativeObjectId(block));
}
@HiddenApi @Implementation
protected final String[] getArrayStringResource(int arrayResId) {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return null;
}
final ResTable res = am.getResources();
final Ref<bag_entry[]> startOfBag = new Ref<>(null);
final int N = res.lockBag(arrayResId, startOfBag);
if (N < 0) {
return null;
}
String[] array = new String[N];
Ref<Res_value> valueRef = new Ref<>(null);
final bag_entry[] bag = startOfBag.get();
int strLen = 0;
for (int i=0; ((int)i)<N; i++) {
valueRef.set(bag[i].map.value);
String str = null;
// Take care of resolving the found resource to its final value.
int block = res.resolveReference(valueRef, bag[i].stringBlock, null);
if (kThrowOnBadId) {
if (block == BAD_INDEX) {
throw new IllegalStateException("Bad resource!");
}
}
if (valueRef.get().dataType == DataType.STRING.code()) {
final ResStringPool pool = res.getTableStringBlock(block);
str = pool.stringAt(valueRef.get().data);
// assume we can skip utf8 vs utf 16 handling
// final char* str8 = pool.string8At(value.data, &strLen);
// if (str8 != NULL) {
// str = env.NewStringUTF(str8);
// } else {
// final char16_t* str16 = pool.stringAt(value.data, &strLen);
// str = env.NewString(reinterpret_cast<final jchar*>(str16),
// strLen);
// }
//
// // If one of our NewString{UTF} calls failed due to memory, an
// // exception will be pending.
// if (env.ExceptionCheck()) {
// res.unlockBag(startOfBag);
// return NULL;
// }
if (str == null) {
res.unlockBag(startOfBag);
return null;
}
array[i] = str;
// str is not NULL at that point, otherwise ExceptionCheck would have been true.
// If we have a large amount of strings in our array, we might
// overflow the local reference table of the VM.
// env.DeleteLocalRef(str);
}
}
res.unlockBag(startOfBag);
return array;
}
@HiddenApi @Implementation
protected final int[] getArrayStringInfo(int arrayResId) {
CppAssetManager am = assetManagerForJavaObject();
ResTable res = am.getResources();
Ref<bag_entry[]> startOfBag = new Ref<>(null);
final int N = res.lockBag(arrayResId, startOfBag);
if (N < 0) {
return null;
}
int[] array = new int[N * 2];
Ref<Res_value> value = new Ref<>(null);
bag_entry[] bag = startOfBag.get();
for (int i = 0, j = 0; i<N; i++) {
int stringIndex = -1;
int stringBlock = 0;
value.set(bag[i].map.value);
// Take care of resolving the found resource to its final value.
stringBlock = res.resolveReference(value, bag[i].stringBlock, null);
if (value.get().dataType == DataType.STRING.code()) {
stringIndex = value.get().data;
}
if (kThrowOnBadId) {
if (stringBlock == BAD_INDEX) {
throw new IllegalStateException("Bad resource!");
}
}
//todo: It might be faster to allocate a C array to contain
// the blocknums and indices, put them in there and then
// do just one SetIntArrayRegion()
//env->SetIntArrayRegion(array, j, 1, &stringBlock);
array[j] = stringBlock;
//env->SetIntArrayRegion(array, j + 1, 1, &stringIndex);
array[j+1] = stringIndex;
j += 2;
}
res.unlockBag(startOfBag);
return array;
}
@HiddenApi @Implementation
public int[] getArrayIntResource(int arrayResId) {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return null;
}
final ResTable res = am.getResources();
// final ResTable::bag_entry* startOfBag;
final Ref<bag_entry[]> startOfBag = new Ref<>(null);
final int N = res.lockBag(arrayResId, startOfBag);
if (N < 0) {
return null;
}
int[] array = new int[N];
if (array == null) {
res.unlockBag(startOfBag);
return null;
}
Ref<Res_value> valueRef = new Ref<>(null);
bag_entry[] bag = startOfBag.get();
for (int i=0; i<N; i++) {
valueRef.set(bag[i].map.value);
// Take care of resolving the found resource to its final value.
int block = res.resolveReference(valueRef, bag[i].stringBlock, null, null, null);
if (kThrowOnBadId) {
if (block == BAD_INDEX) {
res.unlockBag(startOfBag); // seems like this is missing from android_util_AssetManager.cpp?
throw new IllegalStateException("Bad resource!");
// return array;
}
}
Res_value value = valueRef.get();
if (value.dataType >= DataType.TYPE_FIRST_INT
&& value.dataType <= DataType.TYPE_LAST_INT) {
int intVal = value.data;
// env->SetIntArrayRegion(array, i, 1, &intVal);
array[i] = intVal;
}
}
res.unlockBag(startOfBag);
return array;
}
@HiddenApi @Implementation(maxSdk = VERSION_CODES.KITKAT)
protected void init() {
// if (isSystem) {
// verifySystemIdmaps();
// }
init(false);
}
private static CppAssetManager systemCppAssetManager;
@HiddenApi @Implementation(minSdk = VERSION_CODES.KITKAT_WATCH)
protected void init(boolean isSystem) {
// if (isSystem) {
// verifySystemIdmaps();
// }
String androidFrameworkJarPath = RuntimeEnvironment.getAndroidFrameworkJarPath();
Preconditions.checkNotNull(androidFrameworkJarPath);
if (isSystem) {
synchronized (ShadowArscAssetManager.class) {
if (systemCppAssetManager == null) {
systemCppAssetManager = new CppAssetManager();
systemCppAssetManager.addDefaultAssets(androidFrameworkJarPath);
}
}
this.cppAssetManager = systemCppAssetManager;
} else {
this.cppAssetManager = new CppAssetManager();
cppAssetManager.addDefaultAssets(androidFrameworkJarPath);
}
ALOGV("Created AssetManager %s for Java object %s\n", cppAssetManager,
ShadowArscAssetManager.class);
}
@VisibleForTesting
ResTable_config getConfiguration() {
Ref<ResTable_config> config = new Ref<>(new ResTable_config());
assetManagerForJavaObject().getConfiguration(config);
return config.get();
}
// private native final void destroy();
// @HiddenApi
// @Implementation
// public int addOverlayPathNative(String idmapPath) {
// if (Strings.isNullOrEmpty(idmapPath)) {
// return 0;
// }
//
// CppAssetManager am = assetManagerForJavaObject();
// if (am == null) {
// return 0;
// }
// Ref<Integer> cookie = new Ref<>(null);
// boolean res = am.addOverlayPath(new String8(idmapPath), cookie);
// return (res) ? cookie.get() : 0;
// }
@HiddenApi @Implementation
protected int getStringBlockCount() {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return 0;
}
return am.getResources().getTableCount();
}
@Implementation(minSdk = P)
public static long nativeCreate() {
return directlyOn(AssetManager.class, "nativeCreate");
}
synchronized private CppAssetManager assetManagerForJavaObject() {
if (cppAssetManager == null) {
throw new NullPointerException();
}
return cppAssetManager;
}
static ParcelFileDescriptor returnParcelFileDescriptor(Asset a, long[] outOffsets)
throws FileNotFoundException {
Ref<Long> startOffset = new Ref<Long>(-1L);
Ref<Long> length = new Ref<Long>(-1L);;
FileDescriptor fd = a.openFileDescriptor(startOffset, length);
if (fd == null) {
throw new FileNotFoundException(
"This file can not be opened as a file descriptor; it is probably compressed");
}
long[] offsets = outOffsets;
if (offsets == null) {
// fd.close();
return null;
}
offsets[0] = startOffset.get();
offsets[1] = length.get();
// FileDescriptor fileDesc = jniCreateFileDescriptor(fd);
// if (fileDesc == null) {
// close(fd);
// return null;
// }
// TODO: consider doing this
// return new ParcelFileDescriptor(fileDesc);
return ParcelFileDescriptor.open(a.getFile(), ParcelFileDescriptor.MODE_READ_ONLY);
}
/**
* @deprecated Avoid use.
*/
@Deprecated
synchronized public ResTable getCompileTimeResTable() {
if (compileTimeResTable == null) {
CppAssetManager compileTimeCppAssetManager = new CppAssetManager();
for (AssetPath assetPath : assetManagerForJavaObject().getAssetPaths()) {
if (assetPath.isSystem) {
compileTimeCppAssetManager.addDefaultAssets(
RuntimeEnvironment.compileTimeSystemResourcesFile.getPath());
} else {
compileTimeCppAssetManager.addAssetPath(new String8(assetPath.file.getPath()), null, false);
}
}
compileTimeResTable = compileTimeCppAssetManager.getResources();
}
return compileTimeResTable;
}
@Override
Collection<FsFile> getAllAssetDirs() {
ArrayList<FsFile> fsFiles = new ArrayList<>();
for (AssetPath assetPath : cppAssetManager.getAssetPaths()) {
if (assetPath.file.isFile()) {
fsFiles.add(Fs.newJarFile(new File(assetPath.file.getPath())).join("assets"));
} else {
fsFiles.add(assetPath.file);
}
}
return fsFiles;
}
}