org.robolectric.shadows.ShadowArscAssetManager Maven / Gradle / Ivy
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.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.util.reflector.Reflector.reflector;
import android.content.res.AssetManager;
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.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
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.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.Registries;
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;
// native method impls transliterated from
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_util_AssetManager.cpp
@Implements(value = AssetManager.class, maxSdk = VERSION_CODES.O_MR1, shadowPicker = Picker.class)
@SuppressWarnings("NewApi")
public class ShadowArscAssetManager extends ShadowAssetManager.ArscBase {
private static final int STYLE_NUM_ENTRIES = 6;
private static final int STYLE_TYPE = 0;
private static final int STYLE_DATA = 1;
private static final int STYLE_ASSET_COOKIE = 2;
private static final int STYLE_RESOURCE_ID = 3;
private static final int STYLE_CHANGING_CONFIGURATIONS = 4;
private static final int STYLE_DENSITY = 5;
@RealObject
protected AssetManager realObject;
private CppAssetManager cppAssetManager;
@Resetter
public static void reset() {
// todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for resetters...
if (!useLegacy() && RuntimeEnvironment.getApiLevel() < P) {
reflector(_AssetManager_.class).setSystem(null);
// NATIVE_THEME_REGISTRY.clear();
// nativeXMLParserRegistry.clear(); // todo: shouldn't these be freed explicitly? [yes! xw]
// NATIVE_ASSET_REGISTRY.clear();
}
}
@Implementation
protected 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; iGetStringUTFChars(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
protected static void dumpTheme(long theme, int priority, String tag, String prefix) {
throw new UnsupportedOperationException("not yet implemented");
}
@Implementation
protected 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
protected 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
protected String getResourceTypeName(int resid) {
CppAssetManager cppAssetManager = assetManagerForJavaObject();
ResourceName name = new ResourceName();
if (!cppAssetManager.getResources().getResourceName(resid, true, name)) {
return null;
}
return name.type;
}
@Implementation
protected 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 = VERSION_CODES.P)
// public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) {
// throw new UnsupportedOperationException("implement me");
// }
//
@HiddenApi @Implementation(maxSdk = VERSION_CODES.JELLY_BEAN_MR1)
public int addAssetPath(String path) {
return addAssetPathNative(path);
}
@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;
}
final Ref 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(Registries.NATIVE_ASSET_REGISTRY.register(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 = Registries.NATIVE_ASSET_REGISTRY.register(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) {
Registries.NATIVE_ASSET_REGISTRY.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 Registries.NATIVE_ASSET_REGISTRY.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();
final Ref value = new Ref<>(null);
final Ref config = new Ref<>(null);
final Ref 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;
}
}
final Ref 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 getResourceBagValues(int ident, ResTable res) {
// Now lock down the resource object and start pulling stuff from it.
res.lock();
HashMap map;
try {
final Ref entryRef = new Ref<>(null);
final Ref 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;
final Ref valueRef = new Ref<>(null);
final Ref entryRef = new Ref<>(null);
final Ref 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;
}
final Ref ref = new Ref<>(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 = Registries.NATIVE_THEME_REGISTRY.getNativeObject(themeToken);
ResXMLParser xmlParser = xmlParserToken == 0
? null
: Registries.NATIVE_RES_XML_PARSERS.getNativeObject(xmlParserToken);
AttributeResolution.ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes,
attrs, attrs.length, outValues, outIndices);
}
@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 = Registries.NATIVE_THEME_REGISTRY.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 = Registries.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 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();
final Ref 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();
final Ref arrayEnt = new Ref<>(null);
final Ref 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 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 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");
final Ref 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(
am.getResources().getTableStringBlock(block).getNativePtr());
}
@Implementation
protected final SparseArray getAssignedPackageIdentifiers() {
CppAssetManager am = assetManagerForJavaObject();
final ResTable res = am.getResources();
SparseArray 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(Registries.NATIVE_THEME_REGISTRY.register(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) {
Registries.NATIVE_THEME_REGISTRY.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) {
Registries.NATIVE_THEME_REGISTRY.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 = Registries.NATIVE_THEME_REGISTRY.getNativeObject(destPtr);
ResTableTheme src = Registries.NATIVE_THEME_REGISTRY.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(Registries.NATIVE_THEME_REGISTRY.getNativeObject(themeHandle));
ResTable res = theme.getResTable();
final Ref value = new Ref<>(null);
// XXX value could be different in different configs!
final Ref typeSpecFlags = new Ref<>(0);
int block = theme.GetAttribute(ident, value, typeSpecFlags);
final Ref 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 {
final Ref 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(
Registries.NATIVE_RES_XML_TREES.register(block));
}
@HiddenApi @Implementation
protected final String[] getArrayStringResource(int arrayResId) {
CppAssetManager am = assetManagerForJavaObject();
if (am == null) {
return null;
}
final ResTable res = am.getResources();
final Ref startOfBag = new Ref<>(null);
final int N = res.lockBag(arrayResId, startOfBag);
if (N < 0) {
return null;
}
String[] array = new String[N];
final Ref valueRef = new Ref<>(null);
final bag_entry[] bag = startOfBag.get();
int strLen = 0;
for (int i=0; ((int)i)(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();
final Ref startOfBag = new Ref<>(null);
final int N = res.lockBag(arrayResId, startOfBag);
if (N < 0) {
return null;
}
int[] array = new int[N * 2];
final Ref value = new Ref<>(null);
bag_entry[] bag = startOfBag.get();
for (int i = 0, j = 0; iSetIntArrayRegion(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 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;
}
final Ref valueRef = new Ref<>(null);
bag_entry[] bag = startOfBag.get();
for (int i=0; i= 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();
// }
Path 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() {
final Ref 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;
// }
// final Ref 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();
}
synchronized private CppAssetManager assetManagerForJavaObject() {
if (cppAssetManager == null) {
throw new NullPointerException();
}
return cppAssetManager;
}
static ParcelFileDescriptor returnParcelFileDescriptor(Asset a, long[] outOffsets)
throws FileNotFoundException {
final Ref startOffset = new Ref(-1L);
final Ref length = new Ref(-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);
}
@Override
Collection getAllAssetDirs() {
ArrayList paths = new ArrayList<>();
for (AssetPath assetPath : cppAssetManager.getAssetPaths()) {
if (Files.isRegularFile(assetPath.file)) {
paths.add(Fs.forJar(assetPath.file).getPath("assets"));
} else {
paths.add(assetPath.file);
}
}
return paths;
}
@Override
List getAssetPaths() {
return assetManagerForJavaObject().getAssetPaths();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy