| package org.robolectric.res.android; |
| |
| import static com.google.common.primitives.UnsignedBytes.max; |
| import static org.robolectric.res.android.Errors.BAD_INDEX; |
| import static org.robolectric.res.android.Errors.BAD_TYPE; |
| import static org.robolectric.res.android.Errors.BAD_VALUE; |
| import static org.robolectric.res.android.Errors.NO_ERROR; |
| import static org.robolectric.res.android.Errors.NO_MEMORY; |
| import static org.robolectric.res.android.Errors.UNKNOWN_ERROR; |
| import static org.robolectric.res.android.ResourceTypes.RES_STRING_POOL_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_LIBRARY_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_PACKAGE_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE_SPEC_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.validate_chunk; |
| import static org.robolectric.res.android.Util.ALOGD; |
| import static org.robolectric.res.android.Util.ALOGE; |
| import static org.robolectric.res.android.Util.ALOGI; |
| import static org.robolectric.res.android.Util.ALOGV; |
| import static org.robolectric.res.android.Util.ALOGW; |
| import static org.robolectric.res.android.Util.LOG_FATAL_IF; |
| import static org.robolectric.res.android.Util.dtohl; |
| import static org.robolectric.res.android.Util.dtohs; |
| import static org.robolectric.res.android.Util.htodl; |
| import static org.robolectric.res.android.Util.htods; |
| import static org.robolectric.res.android.Util.isTruthy; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.concurrent.Semaphore; |
| import org.robolectric.res.android.ResourceTypes.ResChunk_header; |
| import org.robolectric.res.android.ResourceTypes.ResTable_entry; |
| import org.robolectric.res.android.ResourceTypes.ResTable_header; |
| import org.robolectric.res.android.ResourceTypes.ResTable_map; |
| import org.robolectric.res.android.ResourceTypes.ResTable_map_entry; |
| import org.robolectric.res.android.ResourceTypes.ResTable_package; |
| import org.robolectric.res.android.ResourceTypes.ResTable_sparseTypeEntry; |
| import org.robolectric.res.android.ResourceTypes.ResTable_type; |
| import org.robolectric.res.android.ResourceTypes.ResTable_typeSpec; |
| import org.robolectric.res.android.ResourceTypes.Res_value; |
| |
| // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-8.1.0_r22/libs/androidfw/ResourceTypes.cpp |
| // and https://android.googlesource.com/platform/frameworks/base/+/android-8.1.0_r22/include/androidfw/ResourceTypes.h |
| public class ResTable { |
| |
| private static final int IDMAP_MAGIC = 0x504D4449; |
| private static final int IDMAP_CURRENT_VERSION = 0x00000001; |
| |
| static final int APP_PACKAGE_ID = 0x7f; |
| static final int SYS_PACKAGE_ID = 0x01; |
| |
| static final boolean kDebugStringPoolNoisy = false; |
| static final boolean kDebugXMLNoisy = false; |
| static final boolean kDebugTableNoisy = false; |
| static final boolean kDebugTableGetEntry = false; |
| static final boolean kDebugTableSuperNoisy = false; |
| static final boolean kDebugLoadTableNoisy = false; |
| static final boolean kDebugLoadTableSuperNoisy = false; |
| static final boolean kDebugTableTheme = false; |
| static final boolean kDebugResXMLTree = false; |
| static final boolean kDebugLibNoisy = false; |
| |
| private static final Object NULL = null; |
| public static final bag_set SENTINEL_BAG_SET = new bag_set(1); |
| |
| final Semaphore mLock = new Semaphore(1); |
| |
| // Mutex that controls access to the list of pre-filtered configurations |
| // to check when looking up entries. |
| // When iterating over a bag, the mLock mutex is locked. While mLock is locked, |
| // we do resource lookups. |
| // Mutex is not reentrant, so we must use a different lock than mLock. |
| final Object mFilteredConfigLock = new Object(); |
| |
| // type defined in Errors |
| int mError; |
| |
| ResTable_config mParams; |
| |
| // Array of all resource tables. |
| List<Header> mHeaders = new ArrayList<>(); |
| |
| // Array of packages in all resource tables. |
| final Map<Integer, PackageGroup> mPackageGroups = new HashMap<>(); |
| |
| // Mapping from resource package IDs to indices into the internal |
| // package array. |
| byte[] mPackageMap = new byte[256]; |
| |
| byte mNextPackageId; |
| |
| static boolean Res_CHECKID(int resid) { return ((resid&0xFFFF0000) != 0);} |
| static int Res_GETPACKAGE(int id) { |
| return ((id>>24)-1); |
| } |
| public static int Res_GETTYPE(int id) { |
| return (((id>>16)&0xFF)-1); |
| } |
| static int Res_GETENTRY(int id) { |
| return (id&0xFFFF); |
| } |
| static int Res_MAKEARRAY(int entry) { return (0x02000000 | (entry&0xFFFF)); } |
| static boolean Res_INTERNALID(int resid) { return ((resid&0xFFFF0000) != 0 && (resid&0xFF0000) == 0); } |
| |
| int getResourcePackageIndex(int resID) |
| { |
| return Res_GETPACKAGE(resID) + 1; |
| //return mPackageMap[Res_GETPACKAGE(resID)+1]-1; |
| } |
| |
| // Errors add(final Object data, int size, final int cookie, boolean copyData) { |
| // return addInternal(data, size, NULL, 0, false, cookie, copyData); |
| // } |
| // |
| // Errors add(final Object data, int size, final Object idmapData, int idmapDataSize, |
| // final int cookie, boolean copyData, boolean appAsLib) { |
| // return addInternal(data, size, idmapData, idmapDataSize, appAsLib, cookie, copyData); |
| // } |
| // |
| // Errors add(Asset asset, final int cookie, boolean copyData) { |
| // final Object data = asset.getBuffer(true); |
| // if (data == NULL) { |
| // ALOGW("Unable to get buffer of resource asset file"); |
| // return UNKNOWN_ERROR; |
| // } |
| // |
| // return addInternal(data, static_cast<int>(asset.getLength()), NULL, false, 0, cookie, |
| // copyData); |
| // } |
| |
| // status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false, |
| // bool appAsLib=false, bool isSystemAsset=false); |
| int add( |
| Asset asset, Asset idmapAsset, final int cookie, boolean copyData, |
| boolean appAsLib, boolean isSystemAsset) { |
| final byte[] data = asset.getBuffer(true); |
| if (data == NULL) { |
| ALOGW("Unable to get buffer of resource asset file"); |
| return UNKNOWN_ERROR; |
| } |
| |
| int idmapSize = 0; |
| Object idmapData = NULL; |
| if (idmapAsset != NULL) { |
| idmapData = idmapAsset.getBuffer(true); |
| if (idmapData == NULL) { |
| ALOGW("Unable to get buffer of idmap asset file"); |
| return UNKNOWN_ERROR; |
| } |
| idmapSize = (int) idmapAsset.getLength(); |
| } |
| |
| return addInternal(data, (int) asset.getLength(), |
| idmapData, idmapSize, appAsLib, cookie, copyData, isSystemAsset); |
| } |
| |
| int add(ResTable src, boolean isSystemAsset) |
| { |
| mError = src.mError; |
| |
| for (int i=0; i < src.mHeaders.size(); i++) { |
| mHeaders.add(src.mHeaders.get(i)); |
| } |
| |
| for (PackageGroup srcPg : src.mPackageGroups.values()) { |
| PackageGroup pg = new PackageGroup(this, srcPg.name, srcPg.id, |
| false /* appAsLib */, isSystemAsset || srcPg.isSystemAsset); |
| for (int j=0; j<srcPg.packages.size(); j++) { |
| pg.packages.add(srcPg.packages.get(j)); |
| } |
| |
| for (Integer typeId : srcPg.types.keySet()) { |
| List<Type> typeList = computeIfAbsent(pg.types, typeId, key -> new ArrayList<>()); |
| typeList.addAll(srcPg.types.get(typeId)); |
| } |
| pg.dynamicRefTable.addMappings(srcPg.dynamicRefTable); |
| pg.largestTypeId = max(pg.largestTypeId, srcPg.largestTypeId); |
| mPackageGroups.put(pg.id, pg); |
| } |
| |
| // memcpy(mPackageMap, src->mPackageMap, sizeof(mPackageMap)); |
| System.arraycopy(src.mPackageMap, 0, mPackageMap, 0, mPackageMap.length); |
| |
| return mError; |
| } |
| |
| int addEmpty(final int cookie) { |
| Header header = new Header(this); |
| header.index = mHeaders.size(); |
| header.cookie = cookie; |
| header.values.setToEmpty(); |
| header.ownedData = new byte[ResTable_header.SIZEOF]; |
| |
| ByteBuffer buf = ByteBuffer.wrap(header.ownedData).order(ByteOrder.LITTLE_ENDIAN); |
| ResChunk_header.write(buf, (short) RES_TABLE_TYPE, () -> {}, () -> {}); |
| |
| ResTable_header resHeader = new ResTable_header(buf, 0); |
| // resHeader.header.type = RES_TABLE_TYPE; |
| // resHeader.header.headerSize = sizeof(ResTable_header); |
| // resHeader.header.size = sizeof(ResTable_header); |
| |
| header.header = resHeader; |
| mHeaders.add(header); |
| return (mError=NO_ERROR); |
| } |
| |
| // status_t addInternal(const void* data, size_t size, const void* idmapData, size_t idmapDataSize, |
| // bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset=false); |
| int addInternal(byte[] data, int dataSize, final Object idmapData, int idmapDataSize, |
| boolean appAsLib, final int cookie, boolean copyData, boolean isSystemAsset) |
| { |
| if (!isTruthy(data)) { |
| return NO_ERROR; |
| } |
| |
| if (dataSize < ResTable_header.SIZEOF) { |
| ALOGE("Invalid data. Size(%d) is smaller than a ResTable_header(%d).", |
| (int) dataSize, (int) ResTable_header.SIZEOF); |
| return UNKNOWN_ERROR; |
| } |
| |
| Header header = new Header(this); |
| header.index = mHeaders.size(); |
| header.cookie = cookie; |
| if (idmapData != NULL) { |
| header.resourceIDMap = new int[idmapDataSize / 4]; |
| if (header.resourceIDMap == NULL) { |
| // delete header; |
| return (mError = NO_MEMORY); |
| } |
| // memcpy(header.resourceIDMap, idmapData, idmapDataSize); |
| // header.resourceIDMapSize = idmapDataSize; |
| } |
| mHeaders.add(header); |
| |
| final boolean notDeviceEndian = htods((short) 0xf0) != 0xf0; |
| |
| if (kDebugLoadTableNoisy) { |
| ALOGV("Adding resources to ResTable: data=%s, size=0x%x, cookie=%d, copy=%d " + |
| "idmap=%s\n", data, dataSize, cookie, copyData, idmapData); |
| } |
| |
| if (copyData || notDeviceEndian) { |
| header.ownedData = data; // malloc(dataSize); |
| if (header.ownedData == NULL) { |
| return (mError=NO_MEMORY); |
| } |
| // memcpy(header.ownedData, data, dataSize); |
| data = header.ownedData; |
| } |
| |
| ByteBuffer buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); |
| // header->header = (const ResTable_header*)data; |
| header.header = new ResTable_header(buf, 0); |
| header.size = dtohl(header.header.header.size); |
| if (kDebugLoadTableSuperNoisy) { |
| ALOGI("Got size 0x%x, again size 0x%x, raw size 0x%x\n", header.size, |
| dtohl(header.header.header.size), header.header.header.size); |
| } |
| if (kDebugLoadTableNoisy) { |
| ALOGV("Loading ResTable @%s:\n", header.header); |
| } |
| if (dtohs(header.header.header.headerSize) > header.size |
| || header.size > dataSize) { |
| ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n", |
| (int)dtohs(header.header.header.headerSize), |
| (int)header.size, (int)dataSize); |
| return (mError=BAD_TYPE); |
| } |
| if (((dtohs(header.header.header.headerSize)|header.size)&0x3) != 0) { |
| ALOGW("Bad resource table: header size 0x%x or total size 0x%x is not on an integer boundary\n", |
| (int)dtohs(header.header.header.headerSize), |
| (int)header.size); |
| return (mError=BAD_TYPE); |
| } |
| // header->dataEnd = ((const uint8_t*)header->header) + header->size; |
| header.dataEnd = header.size; |
| |
| // Iterate through all chunks. |
| int curPackage = 0; |
| |
| // const ResChunk_header* chunk = |
| // (const ResChunk_header*)(((const uint8_t*)header->header) |
| // + dtohs(header->header->header.headerSize)); |
| ResChunk_header chunk = |
| new ResChunk_header(buf, dtohs(header.header.header.headerSize)); |
| while (chunk != null && (chunk.myOffset()) <= (header.dataEnd -ResChunk_header.SIZEOF) && |
| (chunk.myOffset()) <= (header.dataEnd -dtohl(chunk.size))) { |
| int err = validate_chunk(chunk, ResChunk_header.SIZEOF, header.dataEnd, "ResTable"); |
| if (err != NO_ERROR) { |
| return (mError=err); |
| } |
| if (kDebugTableNoisy) { |
| ALOGV("Chunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%s\n", |
| dtohs(chunk.type), dtohs(chunk.headerSize), dtohl(chunk.size), |
| (Object)((chunk.myOffset()) - (header.header.myOffset()))); |
| } |
| final int csize = dtohl(chunk.size); |
| final int ctype = dtohs(chunk.type); |
| if (ctype == RES_STRING_POOL_TYPE) { |
| if (header.values.getError() != NO_ERROR) { |
| // Only use the first string chunk; ignore any others that |
| // may appear. |
| err = header.values.setTo(chunk.myBuf(), chunk.myOffset(), csize, false); |
| if (err != NO_ERROR) { |
| return (mError=err); |
| } |
| } else { |
| ALOGW("Multiple string chunks found in resource table."); |
| } |
| } else if (ctype == RES_TABLE_PACKAGE_TYPE) { |
| if (curPackage >= dtohl(header.header.packageCount)) { |
| ALOGW("More package chunks were found than the %d declared in the header.", |
| dtohl(header.header.packageCount)); |
| return (mError=BAD_TYPE); |
| } |
| |
| if (parsePackage( |
| new ResTable_package(chunk.myBuf(), chunk.myOffset()), header, appAsLib, isSystemAsset) != NO_ERROR) { |
| return mError; |
| } |
| curPackage++; |
| } else { |
| ALOGW("Unknown chunk type 0x%x in table at 0x%x.\n", |
| ctype, |
| (chunk.myOffset()) - (header.header.myOffset())); |
| } |
| chunk = chunk.myOffset() + csize < header.dataEnd |
| ? new ResChunk_header(chunk.myBuf(), chunk.myOffset() + csize) |
| : null; |
| } |
| |
| if (curPackage < dtohl(header.header.packageCount)) { |
| ALOGW("Fewer package chunks (%d) were found than the %d declared in the header.", |
| (int)curPackage, dtohl(header.header.packageCount)); |
| return (mError=BAD_TYPE); |
| } |
| mError = header.values.getError(); |
| if (mError != NO_ERROR) { |
| ALOGW("No string values found in resource table!"); |
| } |
| |
| if (kDebugTableNoisy) { |
| ALOGV("Returning from add with mError=%d\n", mError); |
| } |
| return mError; |
| } |
| |
| public final int getResource(int resID, Ref<Res_value> outValue, boolean mayBeBag, int density, |
| Ref<Integer> outSpecFlags, Ref<ResTable_config> outConfig) |
| { |
| if (mError != NO_ERROR) { |
| return mError; |
| } |
| final int p = getResourcePackageIndex(resID); |
| final int t = Res_GETTYPE(resID); |
| final int e = Res_GETENTRY(resID); |
| if (p < 0) { |
| if (Res_GETPACKAGE(resID)+1 == 0) { |
| ALOGW("No package identifier when getting value for resource number 0x%08x", resID); |
| } else { |
| ALOGW("No known package when getting value for resource number 0x%08x", resID); |
| } |
| return BAD_INDEX; |
| } |
| |
| if (t < 0) { |
| ALOGW("No type identifier when getting value for resource number 0x%08x", resID); |
| return BAD_INDEX; |
| } |
| final PackageGroup grp = mPackageGroups.get(p); |
| if (grp == NULL) { |
| ALOGW("Bad identifier when getting value for resource number 0x%08x", resID); |
| return BAD_INDEX; |
| } |
| // Allow overriding density |
| ResTable_config desiredConfig = mParams; |
| if (density > 0) { |
| desiredConfig.density = density; |
| } |
| Entry entry = new Entry(); |
| int err = getEntry(grp, t, e, desiredConfig, entry); |
| if (err != NO_ERROR) { |
| // Only log the failure when we're not running on the host as |
| // part of a tool. The caller will do its own logging. |
| return err; |
| } |
| |
| if ((entry.entry.flags & ResTable_entry.FLAG_COMPLEX) != 0) { |
| if (!mayBeBag) { |
| ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID); |
| } |
| return BAD_VALUE; |
| } |
| |
| // const Res_value* value = reinterpret_cast<const Res_value*>( |
| // reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size); |
| Res_value value = new Res_value(entry.entry.myBuf(), entry.entry.myOffset() + entry.entry.size); |
| |
| // outValue.size = dtohs(value.size); |
| // outValue.res0 = value.res0; |
| // outValue.dataType = value.dataType; |
| // outValue.data = dtohl(value.data); |
| outValue.set(value); |
| |
| // The reference may be pointing to a resource in a shared library. These |
| // references have build-time generated package IDs. These ids may not match |
| // the actual package IDs of the corresponding packages in this ResTable. |
| // We need to fix the package ID based on a mapping. |
| if (grp.dynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) { |
| ALOGW("Failed to resolve referenced package: 0x%08x", outValue.get().data); |
| return BAD_VALUE; |
| } |
| |
| // if (kDebugTableNoisy) { |
| // size_t len; |
| // printf("Found value: pkg=0x%x, type=%d, str=%s, int=%d\n", |
| // entry.package.header.index, |
| // outValue.dataType, |
| // outValue.dataType == Res_value::TYPE_STRING ? |
| // String8(entry.package.header.values.stringAt(outValue.data, &len)).string() : |
| // "", |
| // outValue.data); |
| // } |
| |
| if (outSpecFlags != null) { |
| outSpecFlags.set(entry.specFlags); |
| } |
| if (outConfig != null) { |
| outConfig.set(entry.config); |
| } |
| return entry._package_.header.index; |
| } |
| |
| public final int resolveReference(Ref<Res_value> value, int blockIndex, |
| Ref<Integer> outLastRef) { |
| return resolveReference(value, blockIndex, outLastRef, null, null); |
| } |
| |
| public final int resolveReference(Ref<Res_value> value, int blockIndex, |
| Ref<Integer> outLastRef, Ref<Integer> inoutTypeSpecFlags) { |
| return resolveReference(value, blockIndex, outLastRef, inoutTypeSpecFlags, null); |
| } |
| |
| public final int resolveReference(Ref<Res_value> value, int blockIndex, |
| Ref<Integer> outLastRef, Ref<Integer> inoutTypeSpecFlags, |
| Ref<ResTable_config> outConfig) |
| { |
| int count=0; |
| while (blockIndex >= 0 && value.get().dataType == DataType.REFERENCE.code() |
| && value.get().data != 0 && count < 20) { |
| if (outLastRef != null) { |
| outLastRef.set(value.get().data); |
| } |
| Ref<Integer> newFlags = new Ref<>(0); |
| final int newIndex = getResource(value.get().data, value, true, 0, |
| newFlags, outConfig); |
| if (newIndex == BAD_INDEX) { |
| return BAD_INDEX; |
| } |
| if (kDebugTableTheme) { |
| ALOGI("Resolving reference 0x%x: newIndex=%d, type=0x%x, data=0x%x\n", |
| value.get().data, (int)newIndex, (int)value.get().dataType, value.get().data); |
| } |
| //printf("Getting reference 0x%08x: newIndex=%d\n", value.data, newIndex); |
| if (inoutTypeSpecFlags != null) { |
| inoutTypeSpecFlags.set(inoutTypeSpecFlags.get() | newFlags.get()); |
| } |
| if (newIndex < 0) { |
| // This can fail if the resource being referenced is a style... |
| // in this case, just return the reference, and expect the |
| // caller to deal with. |
| return blockIndex; |
| } |
| blockIndex = newIndex; |
| count++; |
| } |
| return blockIndex; |
| } |
| |
| private interface Compare { |
| boolean compare(ResTable_sparseTypeEntry a, ResTable_sparseTypeEntry b); |
| } |
| |
| ResTable_sparseTypeEntry lower_bound(ResTable_sparseTypeEntry first, ResTable_sparseTypeEntry last, |
| ResTable_sparseTypeEntry value, |
| Compare comparator) { |
| int count = (last.myOffset() - first.myOffset()) / ResTable_sparseTypeEntry.SIZEOF; |
| int itOffset; |
| int step; |
| while (count > 0) { |
| itOffset = first.myOffset(); |
| step = count / 2; |
| itOffset += step * ResTable_sparseTypeEntry.SIZEOF; |
| if (comparator.compare(new ResTable_sparseTypeEntry(first.myBuf(), itOffset), value)) { |
| itOffset += ResTable_sparseTypeEntry.SIZEOF; |
| first = new ResTable_sparseTypeEntry(first.myBuf(), itOffset); |
| } else { |
| count = step; |
| } |
| } |
| return first; |
| } |
| |
| |
| private int getEntry( |
| final PackageGroup packageGroup, int typeIndex, int entryIndex, |
| final ResTable_config config, |
| Entry outEntry) |
| { |
| final List<Type> typeList = getOrDefault(packageGroup.types, typeIndex, Collections.emptyList()); |
| if (typeList.isEmpty()) { |
| ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex); |
| return BAD_TYPE; |
| } |
| |
| ResTable_type bestType = null; |
| int bestOffset = ResTable_type.NO_ENTRY; |
| Package bestPackage = null; |
| int specFlags = 0; |
| byte actualTypeIndex = (byte) typeIndex; |
| ResTable_config bestConfig = null; |
| // memset(&bestConfig, 0, sizeof(bestConfig)); |
| |
| // Iterate over the Types of each package. |
| final int typeCount = typeList.size(); |
| for (int i = 0; i < typeCount; i++) { |
| final Type typeSpec = typeList.get(i); |
| |
| int realEntryIndex = entryIndex; |
| int realTypeIndex = typeIndex; |
| boolean currentTypeIsOverlay = false; |
| |
| // Runtime overlay packages provide a mapping of app resource |
| // ID to package resource ID. |
| if (typeSpec.idmapEntries.hasEntries()) { |
| Ref<Short> overlayEntryIndex = new Ref<>((short) 0); |
| if (typeSpec.idmapEntries.lookup(entryIndex, overlayEntryIndex) != NO_ERROR) { |
| // No such mapping exists |
| continue; |
| } |
| realEntryIndex = overlayEntryIndex.get(); |
| realTypeIndex = typeSpec.idmapEntries.overlayTypeId() - 1; |
| currentTypeIsOverlay = true; |
| } |
| |
| // Check that the entry idx is within range of the declared entry count (ResTable_typeSpec). |
| // Particular types (ResTable_type) may be encoded with sparse entries, and so their |
| // entryCount do not need to match. |
| if (((int) realEntryIndex) >= typeSpec.entryCount) { |
| ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)", |
| Res_MAKEID(packageGroup.id - 1, typeIndex, entryIndex), |
| entryIndex, ((int) typeSpec.entryCount)); |
| // We should normally abort here, but some legacy apps declare |
| // resources in the 'android' package (old bug in AAPT). |
| continue; |
| } |
| |
| // Aggregate all the flags for each package that defines this entry. |
| if (typeSpec.typeSpecFlags != null) { |
| specFlags |= dtohl(typeSpec.typeSpecFlags[realEntryIndex]); |
| } else { |
| specFlags = -1; |
| } |
| |
| List<ResTable_type> candidateConfigs = typeSpec.configs; |
| |
| // List<ResTable_type> filteredConfigs; |
| // if (isTruthy(config) && Objects.equals(mParams, config)) { |
| // // Grab the lock first so we can safely get the current filtered list. |
| // synchronized (mFilteredConfigLock) { |
| // // This configuration is equal to the one we have previously cached for, |
| // // so use the filtered configs. |
| // |
| // final TypeCacheEntry cacheEntry = packageGroup.typeCacheEntries.get(typeIndex); |
| // if (i < cacheEntry.filteredConfigs.size()) { |
| // if (isTruthy(cacheEntry.filteredConfigs.get(i))) { |
| // // Grab a reference to the shared_ptr so it doesn't get destroyed while |
| // // going through this list. |
| // filteredConfigs = cacheEntry.filteredConfigs.get(i); |
| // |
| // // Use this filtered list. |
| // candidateConfigs = filteredConfigs; |
| // } |
| // } |
| // } |
| // } |
| |
| final int numConfigs = candidateConfigs.size(); |
| for (int c = 0; c < numConfigs; c++) { |
| final ResTable_type thisType = candidateConfigs.get(c); |
| if (thisType == NULL) { |
| continue; |
| } |
| |
| final ResTable_config thisConfig; |
| // thisConfig.copyFromDtoH(thisType.config); |
| thisConfig = ResTable_config.fromDtoH(thisType.config); |
| |
| // Check to make sure this one is valid for the current parameters. |
| if (config != NULL && !thisConfig.match(config)) { |
| continue; |
| } |
| |
| // const uint32_t* const eindex = reinterpret_cast<const uint32_t*>( |
| // reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize)); |
| |
| final int eindex = thisType.myOffset() + dtohs(thisType.header.headerSize); |
| |
| int thisOffset; |
| |
| // Check if there is the desired entry in this type. |
| if (isTruthy(thisType.flags & ResTable_type.FLAG_SPARSE)) { |
| // This is encoded as a sparse map, so perform a binary search. |
| final ByteBuffer buf = thisType.myBuf(); |
| ResTable_sparseTypeEntry sparseIndices = new ResTable_sparseTypeEntry(buf, eindex); |
| ResTable_sparseTypeEntry result = lower_bound( |
| sparseIndices, |
| new ResTable_sparseTypeEntry(buf, sparseIndices.myOffset() + dtohl(thisType.entryCount)), |
| new ResTable_sparseTypeEntry(buf, realEntryIndex), |
| (a, b) -> dtohs(a.idxOrOffset) < dtohs(b.idxOrOffset)); |
| // if (result == sparseIndices + dtohl(thisType.entryCount) |
| // || dtohs(result.idx) != realEntryIndex) { |
| if (result.myOffset() == sparseIndices.myOffset() + dtohl(thisType.entryCount) |
| || dtohs(result.idxOrOffset) != realEntryIndex) { |
| // No entry found. |
| continue; |
| } |
| // Extract the offset from the entry. Each offset must be a multiple of 4 |
| // so we store it as the real offset divided by 4. |
| // thisOffset = dtohs(result->offset) * 4u; |
| thisOffset = dtohs(result.idxOrOffset) * 4; |
| } else { |
| if (realEntryIndex >= dtohl(thisType.entryCount)) { |
| // Entry does not exist. |
| continue; |
| } |
| // thisOffset = dtohl(eindex[realEntryIndex]); |
| thisOffset = thisType.entryOffset(realEntryIndex); |
| } |
| |
| if (thisOffset == ResTable_type.NO_ENTRY) { |
| // There is no entry for this index and configuration. |
| continue; |
| } |
| |
| if (bestType != NULL) { |
| // Check if this one is less specific than the last found. If so, |
| // we will skip it. We check starting with things we most care |
| // about to those we least care about. |
| if (!thisConfig.isBetterThan(bestConfig, config)) { |
| if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) { |
| continue; |
| } |
| } |
| } |
| |
| bestType = thisType; |
| bestOffset = thisOffset; |
| bestConfig = thisConfig; |
| bestPackage = typeSpec._package_; |
| actualTypeIndex = (byte) realTypeIndex; |
| |
| // If no config was specified, any type will do, so skip |
| if (config == NULL) { |
| break; |
| } |
| } |
| } |
| |
| if (bestType == NULL) { |
| return BAD_INDEX; |
| } |
| |
| bestOffset += dtohl(bestType.entriesStart); |
| |
| // if (bestOffset > (dtohl(bestType->header.size)-sizeof(ResTable_entry))) { |
| if (bestOffset > (dtohl(bestType.header.size)- ResTable_entry.SIZEOF)) { |
| ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x", |
| bestOffset, dtohl(bestType.header.size)); |
| return BAD_TYPE; |
| } |
| if ((bestOffset & 0x3) != 0) { |
| ALOGW("ResTable_entry at 0x%x is not on an integer boundary", bestOffset); |
| return BAD_TYPE; |
| } |
| |
| // const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>( |
| // reinterpret_cast<const uint8_t*>(bestType) + bestOffset); |
| final ResTable_entry entry = new ResTable_entry(bestType.myBuf(), |
| bestType.myOffset() + bestOffset); |
| if (dtohs(entry.size) < ResTable_entry.SIZEOF) { |
| ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry.size)); |
| return BAD_TYPE; |
| } |
| |
| if (outEntry != null) { |
| outEntry.entry = entry; |
| outEntry.config = bestConfig; |
| outEntry.type = bestType; |
| outEntry.specFlags = specFlags; |
| outEntry._package_ = bestPackage; |
| outEntry.typeStr = new StringPoolRef(bestPackage.typeStrings, actualTypeIndex - bestPackage.typeIdOffset); |
| outEntry.keyStr = new StringPoolRef(bestPackage.keyStrings, dtohl(entry.key.index)); |
| } |
| return NO_ERROR; |
| } |
| |
| int parsePackage(ResTable_package pkg, |
| Header header, boolean appAsLib, boolean isSystemAsset) |
| { |
| int base = pkg.myOffset(); |
| int err = validate_chunk(pkg.header, ResTable_package.SIZEOF - 4 /*sizeof(pkg.typeIdOffset)*/, |
| header.dataEnd, "ResTable_package"); |
| if (err != NO_ERROR) { |
| return (mError=err); |
| } |
| |
| final int pkgSize = dtohl(pkg.header.size); |
| |
| if (dtohl(pkg.typeStrings) >= pkgSize) { |
| ALOGW("ResTable_package type strings at 0x%x are past chunk size 0x%x.", |
| dtohl(pkg.typeStrings), pkgSize); |
| return (mError=BAD_TYPE); |
| } |
| if ((dtohl(pkg.typeStrings)&0x3) != 0) { |
| ALOGW("ResTable_package type strings at 0x%x is not on an integer boundary.", |
| dtohl(pkg.typeStrings)); |
| return (mError=BAD_TYPE); |
| } |
| if (dtohl(pkg.keyStrings) >= pkgSize) { |
| ALOGW("ResTable_package key strings at 0x%x are past chunk size 0x%x.", |
| dtohl(pkg.keyStrings), pkgSize); |
| return (mError=BAD_TYPE); |
| } |
| if ((dtohl(pkg.keyStrings)&0x3) != 0) { |
| ALOGW("ResTable_package key strings at 0x%x is not on an integer boundary.", |
| dtohl(pkg.keyStrings)); |
| return (mError=BAD_TYPE); |
| } |
| |
| int id = dtohl(pkg.id); |
| final Map<Byte, IdmapEntries> idmapEntries = new HashMap<>(); |
| |
| if (header.resourceIDMap != NULL) { |
| // byte targetPackageId = 0; |
| // int err = parseIdmap(header.resourceIDMap, header.resourceIDMapSize, &targetPackageId, &idmapEntries); |
| // if (err != NO_ERROR) { |
| // ALOGW("Overlay is broken"); |
| // return (mError=err); |
| // } |
| // id = targetPackageId; |
| } |
| |
| if (id >= 256) { |
| // LOG_ALWAYS_FATAL("Package id out of range"); |
| throw new IllegalStateException("Package id out of range"); |
| // return NO_ERROR; |
| } else if (id == 0 || (id == 0x7f && appAsLib) || isSystemAsset) { |
| // This is a library or a system asset, so assign an ID |
| id = mNextPackageId++; |
| } |
| |
| PackageGroup group = null; |
| Package _package = new Package(this, header, pkg); |
| if (_package == NULL) { |
| return (mError=NO_MEMORY); |
| } |
| |
| // err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings), |
| // header->dataEnd-(base+dtohl(pkg->typeStrings))); |
| err = _package.typeStrings.setTo(pkg.myBuf(), base+dtohl(pkg.typeStrings), |
| header.dataEnd -(base+dtohl(pkg.typeStrings)), false); |
| if (err != NO_ERROR) { |
| // delete group; |
| // delete _package; |
| return (mError=err); |
| } |
| |
| // err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings), |
| // header->dataEnd-(base+dtohl(pkg->keyStrings))); |
| err = _package.keyStrings.setTo(pkg.myBuf(), base+dtohl(pkg.keyStrings), |
| header.dataEnd -(base+dtohl(pkg.keyStrings)), false); |
| if (err != NO_ERROR) { |
| // delete group; |
| // delete _package; |
| return (mError=err); |
| } |
| |
| int idx = mPackageMap[id]; |
| if (idx == 0) { |
| idx = mPackageGroups.size() + 1; |
| |
| // char[] tmpName = new char[pkg.name.length /*sizeof(pkg.name)/sizeof(pkg.name[0])*/]; |
| // strcpy16_dtoh(tmpName, pkg.name, sizeof(pkg.name)/sizeof(pkg.name[0])); |
| group = new PackageGroup(this, new String(pkg.name), id, appAsLib, isSystemAsset); |
| if (group == NULL) { |
| // delete _package; |
| return (mError=NO_MEMORY); |
| } |
| |
| mPackageGroups.put(group.id, group); |
| // if (err < NO_ERROR) { |
| // return (mError=err); |
| // } |
| |
| mPackageMap[id] = (byte) idx; |
| |
| // Find all packages that reference this package |
| // int N = mPackageGroups.size(); |
| // for (int i = 0; i < N; i++) { |
| for (PackageGroup packageGroup : mPackageGroups.values()) { |
| packageGroup.dynamicRefTable.addMapping( |
| group.name, (byte) group.id); |
| } |
| } else { |
| group = mPackageGroups.get(idx - 1); |
| if (group == NULL) { |
| return (mError=UNKNOWN_ERROR); |
| } |
| } |
| |
| group.packages.add(_package); |
| // if (err < NO_ERROR) { |
| // return (mError=err); |
| // } |
| |
| // Iterate through all chunks. |
| ResChunk_header chunk = |
| new ResChunk_header(pkg.myBuf(), pkg.myOffset() + dtohs(pkg.header.headerSize)); |
| // const uint8_t* endPos = ((const uint8_t*)pkg) + dtohs(pkg->header.size); |
| final int endPos = (pkg.myOffset()) + pkg.header.size; |
| // while (((const uint8_t*)chunk) <= (endPos-sizeof(ResChunk_header)) && |
| // ((const uint8_t*)chunk) <= (endPos-dtohl(chunk->size))) { |
| while (chunk != null && (chunk.myOffset()) <= (endPos-ResChunk_header.SIZEOF) && |
| (chunk.myOffset()) <= (endPos-dtohl(chunk.size))) { |
| if (kDebugTableNoisy) { |
| ALOGV("PackageChunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%s\n", |
| dtohs(chunk.type), dtohs(chunk.headerSize), dtohl(chunk.size), |
| ((chunk.myOffset()) - (header.header.myOffset()))); |
| } |
| final int csize = dtohl(chunk.size); |
| final short ctype = dtohs(chunk.type); |
| if (ctype == RES_TABLE_TYPE_SPEC_TYPE) { |
| final ResTable_typeSpec typeSpec = new ResTable_typeSpec(chunk.myBuf(), chunk.myOffset()); |
| err = validate_chunk(typeSpec.header, ResTable_typeSpec.SIZEOF, |
| endPos, "ResTable_typeSpec"); |
| if (err != NO_ERROR) { |
| return (mError=err); |
| } |
| |
| final int typeSpecSize = dtohl(typeSpec.header.size); |
| final int newEntryCount = dtohl(typeSpec.entryCount); |
| |
| if (kDebugLoadTableNoisy) { |
| ALOGI("TypeSpec off %s: type=0x%x, headerSize=0x%x, size=%s\n", |
| (base-chunk.myOffset()), |
| dtohs(typeSpec.header.type), |
| dtohs(typeSpec.header.headerSize), |
| typeSpecSize); |
| } |
| // look for block overrun or int overflow when multiplying by 4 |
| if ((dtohl(typeSpec.entryCount) > (Integer.MAX_VALUE/4 /*sizeof(int)*/) |
| || dtohs(typeSpec.header.headerSize)+(4 /*sizeof(int)*/*newEntryCount) |
| > typeSpecSize)) { |
| ALOGW("ResTable_typeSpec entry index to %s extends beyond chunk end %s.", |
| (dtohs(typeSpec.header.headerSize) + (4 /*sizeof(int)*/*newEntryCount)), |
| typeSpecSize); |
| return (mError=BAD_TYPE); |
| } |
| |
| if (typeSpec.id == 0) { |
| ALOGW("ResTable_type has an id of 0."); |
| return (mError=BAD_TYPE); |
| } |
| |
| if (newEntryCount > 0) { |
| boolean addToType = true; |
| byte typeIndex = (byte) (typeSpec.id - 1); |
| IdmapEntries idmapEntry = idmapEntries.get(typeSpec.id); |
| if (idmapEntry != null) { |
| typeIndex = (byte) (idmapEntry.targetTypeId() - 1); |
| } else if (header.resourceIDMap != NULL) { |
| // This is an overlay, but the types in this overlay are not |
| // overlaying anything according to the idmap. We can skip these |
| // as they will otherwise conflict with the other resources in the package |
| // without a mapping. |
| addToType = false; |
| } |
| |
| if (addToType) { |
| List<Type> typeList = computeIfAbsent(group.types, (int) typeIndex, k -> new ArrayList<>()); |
| if (!typeList.isEmpty()) { |
| final Type existingType = typeList.get(0); |
| if (existingType.entryCount != newEntryCount && idmapEntry == null) { |
| ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d", |
| (int) newEntryCount, (int) existingType.entryCount); |
| // We should normally abort here, but some legacy apps declare |
| // resources in the 'android' package (old bug in AAPT). |
| } |
| } |
| |
| Type t = new Type(header, _package, newEntryCount); |
| t.typeSpec = typeSpec; |
| t.typeSpecFlags = typeSpec.getSpecFlags(); |
| if (idmapEntry != null) { |
| t.idmapEntries = idmapEntry; |
| } |
| typeList.add(t); |
| group.largestTypeId = max(group.largestTypeId, typeSpec.id); |
| } |
| } else { |
| ALOGV("Skipping empty ResTable_typeSpec for type %d", typeSpec.id); |
| } |
| |
| } else if (ctype == RES_TABLE_TYPE_TYPE) { |
| ResTable_type type = new ResTable_type(chunk.myBuf(), chunk.myOffset()); |
| err = validate_chunk(type.header, ResTable_type.SIZEOF_WITHOUT_CONFIG/*-sizeof(ResTable_config)*/+4, |
| endPos, "ResTable_type"); |
| if (err != NO_ERROR) { |
| return (mError=err); |
| } |
| |
| final int typeSize = dtohl(type.header.size); |
| final int newEntryCount = dtohl(type.entryCount); |
| |
| if (kDebugLoadTableNoisy) { |
| System.out.println(String.format("Type off 0x%x: type=0x%x, headerSize=0x%x, size=%d\n", |
| base-chunk.myOffset(), |
| dtohs(type.header.type), |
| dtohs(type.header.headerSize), |
| typeSize)); |
| } |
| if (dtohs(type.header.headerSize)+(4/*sizeof(int)*/*newEntryCount) > typeSize) { |
| ALOGW("ResTable_type entry index to %s extends beyond chunk end 0x%x.", |
| (dtohs(type.header.headerSize) + (4/*sizeof(int)*/*newEntryCount)), |
| typeSize); |
| return (mError=BAD_TYPE); |
| } |
| |
| if (newEntryCount != 0 |
| && dtohl(type.entriesStart) > (typeSize- ResTable_entry.SIZEOF)) { |
| ALOGW("ResTable_type entriesStart at 0x%x extends beyond chunk end 0x%x.", |
| dtohl(type.entriesStart), typeSize); |
| return (mError=BAD_TYPE); |
| } |
| |
| if (type.id == 0) { |
| ALOGW("ResTable_type has an id of 0."); |
| return (mError=BAD_TYPE); |
| } |
| |
| if (newEntryCount > 0) { |
| boolean addToType = true; |
| byte typeIndex = (byte) (type.id - 1); |
| IdmapEntries idmapEntry = idmapEntries.get(type.id); |
| if (idmapEntry != null) { |
| typeIndex = (byte) (idmapEntry.targetTypeId() - 1); |
| } else if (header.resourceIDMap != NULL) { |
| // This is an overlay, but the types in this overlay are not |
| // overlaying anything according to the idmap. We can skip these |
| // as they will otherwise conflict with the other resources in the package |
| // without a mapping. |
| addToType = false; |
| } |
| |
| if (addToType) { |
| List<Type> typeList = getOrDefault(group.types, (int) typeIndex, Collections.emptyList()); |
| if (typeList.isEmpty()) { |
| ALOGE("No TypeSpec for type %d", type.id); |
| return (mError = BAD_TYPE); |
| } |
| |
| Type t = typeList.get(typeList.size() - 1); |
| if (newEntryCount != t.entryCount) { |
| ALOGE("ResTable_type entry count inconsistent: given %d, previously %d", |
| (int) newEntryCount, (int) t.entryCount); |
| return (mError = BAD_TYPE); |
| } |
| |
| if (t._package_ != _package) { |
| ALOGE("No TypeSpec for type %d", type.id); |
| return (mError = BAD_TYPE); |
| } |
| |
| t.configs.add(type); |
| |
| if (kDebugTableGetEntry) { |
| ResTable_config thisConfig = ResTable_config.fromDtoH(type.config); |
| ALOGI("Adding config to type %d: %s\n", type.id, |
| thisConfig.toString()); |
| } |
| } |
| } else { |
| ALOGV("Skipping empty ResTable_type for type %d", type.id); |
| } |
| |
| } else if (ctype == RES_TABLE_LIBRARY_TYPE) { |
| if (group.dynamicRefTable.entries().isEmpty()) { |
| throw new UnsupportedOperationException("libraries not supported yet"); |
| // int err = group.dynamicRefTable.load(new ResTable_lib_header(chunk.myBuf(), chunk.myOffset()); |
| // if (err != NO_ERROR) { |
| // return (mError=err); |
| // } |
| // |
| // // Fill in the reference table with the entries we already know about. |
| // size_t N = mPackageGroups.size(); |
| // for (size_t i = 0; i < N; i++) { |
| // group.dynamicRefTable.addMapping(mPackageGroups[i].name, mPackageGroups[i].id); |
| // } |
| } else { |
| ALOGW("Found multiple library tables, ignoring..."); |
| } |
| } else { |
| err = validate_chunk(chunk, ResChunk_header.SIZEOF, |
| endPos, "ResTable_package:unknown"); |
| if (err != NO_ERROR) { |
| return (mError=err); |
| } |
| } |
| chunk = chunk.myOffset() + csize < endPos ? new ResChunk_header(chunk.myBuf(), chunk.myOffset() + csize) : null; |
| } |
| |
| return NO_ERROR; |
| } |
| |
| public int getTableCookie(int index) { |
| return mHeaders.get(index).cookie; |
| } |
| |
| void setParameters(ResTable_config params) |
| { |
| // AutoMutex _lock(mLock); |
| // AutoMutex _lock2(mFilteredConfigLock); |
| synchronized (mLock) { |
| synchronized (mFilteredConfigLock) { |
| if (kDebugTableGetEntry) { |
| ALOGI("Setting parameters: %s\n", params.toString()); |
| } |
| mParams = params; |
| for (PackageGroup packageGroup : mPackageGroups.values()) { |
| if (kDebugTableNoisy) { |
| ALOGI("CLEARING BAGS FOR GROUP 0x%x!", packageGroup.id); |
| } |
| packageGroup.clearBagCache(); |
| |
| // Find which configurations match the set of parameters. This allows for a much |
| // faster lookup in getEntry() if the set of values is narrowed down. |
| //for (int t = 0; t < packageGroup.types.size(); t++) { |
| //if (packageGroup.types.get(t).isEmpty()) { |
| // continue; |
| // } |
| // |
| // List<Type> typeList = packageGroup.types.get(t); |
| for (List<Type> typeList : packageGroup.types.values()) { |
| if (typeList.isEmpty()) { |
| continue; |
| } |
| |
| // Retrieve the cache entry for this type. |
| //TypeCacheEntry cacheEntry = packageGroup.typeCacheEntries.editItemAt(t); |
| |
| for (int ts = 0; ts < typeList.size(); ts++) { |
| Type type = typeList.get(ts); |
| |
| // std::shared_ptr<Vector<const ResTable_type*>> newFilteredConfigs = |
| // std::make_shared<Vector<const ResTable_type*>>(); |
| List<ResTable_type> newFilteredConfigs = new ArrayList<>(); |
| |
| for (int ti = 0; ti < type.configs.size(); ti++) { |
| ResTable_config config = ResTable_config.fromDtoH(type.configs.get(ti).config); |
| |
| if (config.match(mParams)) { |
| newFilteredConfigs.add(type.configs.get(ti)); |
| } |
| } |
| |
| if (kDebugTableNoisy) { |
| ALOGD("Updating pkg=0x%x type=0x%x with 0x%x filtered configs", |
| packageGroup.id, ts, newFilteredConfigs.size()); |
| } |
| |
| // todo: implement cache |
| // cacheEntry.filteredConfigs.add(newFilteredConfigs); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| ResTable_config getParameters() |
| { |
| // mLock.lock(); |
| synchronized (mLock) { |
| return mParams; |
| } |
| // mLock.unlock(); |
| } |
| |
| private static final Map<String, Integer> sInternalNameToIdMap = new HashMap<>(); |
| static { |
| sInternalNameToIdMap.put("^type", ResTable_map.ATTR_TYPE); |
| sInternalNameToIdMap.put("^l10n", ResTable_map.ATTR_L10N); |
| sInternalNameToIdMap.put("^min" , ResTable_map.ATTR_MIN); |
| sInternalNameToIdMap.put("^max", ResTable_map.ATTR_MAX); |
| sInternalNameToIdMap.put("^other", ResTable_map.ATTR_OTHER); |
| sInternalNameToIdMap.put("^zero", ResTable_map.ATTR_ZERO); |
| sInternalNameToIdMap.put("^one", ResTable_map.ATTR_ONE); |
| sInternalNameToIdMap.put("^two", ResTable_map.ATTR_TWO); |
| sInternalNameToIdMap.put("^few", ResTable_map.ATTR_FEW); |
| sInternalNameToIdMap.put("^many", ResTable_map.ATTR_MANY); |
| } |
| |
| public int identifierForName(String name, String type, String packageName) { |
| return identifierForName(name, type, packageName, null); |
| } |
| |
| public int identifierForName(String nameString, String type, String packageName, |
| Ref<Integer> outTypeSpecFlags) { |
| // if (kDebugTableSuperNoisy) { |
| // printf("Identifier for name: error=%d\n", mError); |
| // } |
| // // Check for internal resource identifier as the very first thing, so |
| // // that we will always find them even when there are no resources. |
| if (nameString.startsWith("^")) { |
| if (sInternalNameToIdMap.containsKey(nameString)) { |
| if (outTypeSpecFlags != null) { |
| outTypeSpecFlags.set(ResTable_typeSpec.SPEC_PUBLIC); |
| } |
| return sInternalNameToIdMap.get(nameString); |
| } |
| if (nameString.length() > 7) |
| if (nameString.substring(1, 6).equals("index_")) { |
| int index = Integer.getInteger(nameString.substring(7)); |
| if (Res_CHECKID(index)) { |
| ALOGW("Array resource index: %d is too large.", |
| index); |
| return 0; |
| } |
| if (outTypeSpecFlags != null) { |
| outTypeSpecFlags.set(ResTable_typeSpec.SPEC_PUBLIC); |
| } |
| return Res_MAKEARRAY(index); |
| } |
| |
| return 0; |
| } |
| |
| if (mError != NO_ERROR) { |
| return 0; |
| } |
| |
| |
| // Figure out the package and type we are looking in... |
| // TODO(BC): The following code block was a best effort attempt to directly transliterate |
| // C++ code which uses pointer artihmetic. Consider replacing with simpler logic |
| |
| boolean fakePublic = false; |
| char[] name = nameString.toCharArray(); |
| int packageEnd = -1; |
| int typeEnd = -1; |
| int nameEnd = name.length; |
| int pIndex = 0; |
| while (pIndex < nameEnd) { |
| char p = name[pIndex]; |
| if (p == ':') packageEnd = pIndex; |
| else if (p == '/') typeEnd = pIndex; |
| pIndex++; |
| } |
| int nameIndex = 0; |
| if (name[nameIndex] == '@') { |
| nameIndex++; |
| if (name[nameIndex] == '*') { |
| fakePublic = true; |
| nameIndex++; |
| } |
| } |
| if (nameIndex >= nameEnd) { |
| return 0; |
| } |
| if (packageEnd != -1) { |
| packageName = nameString.substring(nameIndex, packageEnd); |
| nameIndex = packageEnd+1; |
| } else if (packageName == null) { |
| return 0; |
| } |
| if (typeEnd != -1) { |
| type = nameString.substring(nameIndex, typeEnd); |
| nameIndex = typeEnd+1; |
| } else if (type == null) { |
| return 0; |
| } |
| if (nameIndex >= nameEnd) { |
| return 0; |
| } |
| nameString = nameString.substring(nameIndex, nameEnd); |
| |
| // nameLen = nameEnd-name; |
| // if (kDebugTableNoisy) { |
| // printf("Looking for identifier: type=%s, name=%s, package=%s\n", |
| // String8(type, typeLen).string(), |
| // String8(name, nameLen).string(), |
| // String8(package, packageLen).string()); |
| // } |
| final String attr = "attr"; |
| final String attrPrivate = "^attr-private"; |
| for (PackageGroup group : mPackageGroups.values()) { |
| if (!Objects.equals(packageName.trim(), group.name.trim())) { |
| if (kDebugTableNoisy) { |
| System.out.println(String.format("Skipping package group: %s\n", group.name)); |
| } |
| continue; |
| } |
| for (Package pkg : group.packages) { |
| String targetType = type; |
| |
| do { |
| int ti = pkg.typeStrings.indexOfString(targetType); |
| if (ti < 0) { |
| continue; |
| } |
| ti += pkg.typeIdOffset; |
| int identifier = findEntry(group, ti, nameString, outTypeSpecFlags); |
| if (identifier != 0) { |
| if (fakePublic && outTypeSpecFlags != null) { |
| outTypeSpecFlags.set(outTypeSpecFlags.get() | ResTable_typeSpec.SPEC_PUBLIC); |
| } |
| return identifier; |
| } |
| } while (attr.compareTo(targetType) == 0 |
| && ((targetType = attrPrivate) != null) |
| ); |
| } |
| break; |
| } |
| return 0; |
| } |
| |
| int findEntry(PackageGroup group, int typeIndex, String name, Ref<Integer> outTypeSpecFlags) { |
| List<Type> typeList = getOrDefault(group.types, typeIndex, Collections.emptyList()); |
| for (Type type : typeList) { |
| int ei = type._package_.keyStrings.indexOfString(name); |
| if (ei < 0) { |
| continue; |
| } |
| for (ResTable_type resTableType : type.configs) { |
| int entryIndex = resTableType.findEntryByResName(ei); |
| if (entryIndex >= 0) { |
| int resId = Res_MAKEID(group.id - 1, typeIndex, entryIndex); |
| if (outTypeSpecFlags != null) { |
| Entry result = new Entry(); |
| if (getEntry(group, typeIndex, entryIndex, null, result) != NO_ERROR) { |
| ALOGW("Failed to find spec flags for 0x%08x", resId); |
| return 0; |
| } |
| outTypeSpecFlags.set(result.specFlags); |
| } |
| return resId; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| //bool ResTable::expandResourceRef(const char16_t* refStr, size_t refLen, |
| // String16* outPackage, |
| // String16* outType, |
| // String16* outName, |
| // const String16* defType, |
| // const String16* defPackage, |
| // const char** outErrorMsg, |
| // bool* outPublicOnly) |
| //{ |
| // const char16_t* packageEnd = NULL; |
| // const char16_t* typeEnd = NULL; |
| // const char16_t* p = refStr; |
| // const char16_t* const end = p + refLen; |
| // while (p < end) { |
| // if (*p == ':') packageEnd = p; |
| // else if (*p == '/') { |
| // typeEnd = p; |
| // break; |
| // } |
| // p++; |
| // } |
| // p = refStr; |
| // if (*p == '@') p++; |
| // |
| // if (outPublicOnly != NULL) { |
| // *outPublicOnly = true; |
| // } |
| // if (*p == '*') { |
| // p++; |
| // if (outPublicOnly != NULL) { |
| // *outPublicOnly = false; |
| // } |
| // } |
| // |
| // if (packageEnd) { |
| // *outPackage = String16(p, packageEnd-p); |
| // p = packageEnd+1; |
| // } else { |
| // if (!defPackage) { |
| // if (outErrorMsg) { |
| // *outErrorMsg = "No resource package specified"; |
| // } |
| // return false; |
| // } |
| // *outPackage = *defPackage; |
| // } |
| // if (typeEnd) { |
| // *outType = String16(p, typeEnd-p); |
| // p = typeEnd+1; |
| // } else { |
| // if (!defType) { |
| // if (outErrorMsg) { |
| // *outErrorMsg = "No resource type specified"; |
| // } |
| // return false; |
| // } |
| // *outType = *defType; |
| // } |
| // *outName = String16(p, end-p); |
| // if(**outPackage == 0) { |
| // if(outErrorMsg) { |
| // *outErrorMsg = "Resource package cannot be an empty string"; |
| // } |
| // return false; |
| // } |
| // if(**outType == 0) { |
| // if(outErrorMsg) { |
| // *outErrorMsg = "Resource type cannot be an empty string"; |
| // } |
| // return false; |
| // } |
| // if(**outName == 0) { |
| // if(outErrorMsg) { |
| // *outErrorMsg = "Resource id cannot be an empty string"; |
| // } |
| // return false; |
| // } |
| // return true; |
| //} |
| // |
| //static uint32_t get_hex(char c, bool* outError) |
| //{ |
| // if (c >= '0' && c <= '9') { |
| // return c - '0'; |
| // } else if (c >= 'a' && c <= 'f') { |
| // return c - 'a' + 0xa; |
| // } else if (c >= 'A' && c <= 'F') { |
| // return c - 'A' + 0xa; |
| // } |
| // *outError = true; |
| // return 0; |
| //} |
| // |
| //struct unit_entry |
| //{ |
| // const char* name; |
| // size_t len; |
| // uint8_t type; |
| // uint32_t unit; |
| // float scale; |
| //}; |
| // |
| //static const unit_entry unitNames[] = { |
| // { "px", strlen("px"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_PX, 1.0f }, |
| // { "dip", strlen("dip"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_DIP, 1.0f }, |
| // { "dp", strlen("dp"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_DIP, 1.0f }, |
| // { "sp", strlen("sp"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_SP, 1.0f }, |
| // { "pt", strlen("pt"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_PT, 1.0f }, |
| // { "in", strlen("in"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_IN, 1.0f }, |
| // { "mm", strlen("mm"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_MM, 1.0f }, |
| // { "%", strlen("%"), Res_value::TYPE_FRACTION, Res_value::COMPLEX_UNIT_FRACTION, 1.0f/100 }, |
| // { "%s", strlen("%s"), Res_value::TYPE_FRACTION, Res_value::COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100 }, |
| // { NULL, 0, 0, 0, 0 } |
| //}; |
| // |
| //static bool parse_unit(const char* str, Res_value* outValue, |
| // float* outScale, const char** outEnd) |
| //{ |
| // const char* end = str; |
| // while (*end != 0 && !isspace((unsigned char)*end)) { |
| // end++; |
| // } |
| // const size_t len = end-str; |
| // |
| // const char* realEnd = end; |
| // while (*realEnd != 0 && isspace((unsigned char)*realEnd)) { |
| // realEnd++; |
| // } |
| // if (*realEnd != 0) { |
| // return false; |
| // } |
| // |
| // const unit_entry* cur = unitNames; |
| // while (cur->name) { |
| // if (len == cur->len && strncmp(cur->name, str, len) == 0) { |
| // outValue->dataType = cur->type; |
| // outValue->data = cur->unit << Res_value::COMPLEX_UNIT_SHIFT; |
| // *outScale = cur->scale; |
| // *outEnd = end; |
| // //printf("Found unit %s for %s\n", cur->name, str); |
| // return true; |
| // } |
| // cur++; |
| // } |
| // |
| // return false; |
| //} |
| // |
| //bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue) |
| //{ |
| // while (len > 0 && isspace16(*s)) { |
| // s++; |
| // len--; |
| // } |
| // |
| // if (len <= 0) { |
| // return false; |
| // } |
| // |
| // size_t i = 0; |
| // int64_t val = 0; |
| // bool neg = false; |
| // |
| // if (*s == '-') { |
| // neg = true; |
| // i++; |
| // } |
| // |
| // if (s[i] < '0' || s[i] > '9') { |
| // return false; |
| // } |
| // |
| // static_assert(std::is_same<uint32_t, Res_value::data_type>::value, |
| // "Res_value::data_type has changed. The range checks in this " |
| // "function are no longer correct."); |
| // |
| // // Decimal or hex? |
| // bool isHex; |
| // if (len > 1 && s[i] == '0' && s[i+1] == 'x') { |
| // isHex = true; |
| // i += 2; |
| // |
| // if (neg) { |
| // return false; |
| // } |
| // |
| // if (i == len) { |
| // // Just u"0x" |
| // return false; |
| // } |
| // |
| // bool error = false; |
| // while (i < len && !error) { |
| // val = (val*16) + get_hex(s[i], &error); |
| // i++; |
| // |
| // if (val > std::numeric_limits<uint32_t>::max()) { |
| // return false; |
| // } |
| // } |
| // if (error) { |
| // return false; |
| // } |
| // } else { |
| // isHex = false; |
| // while (i < len) { |
| // if (s[i] < '0' || s[i] > '9') { |
| // return false; |
| // } |
| // val = (val*10) + s[i]-'0'; |
| // i++; |
| // |
| // if ((neg && -val < std::numeric_limits<int32_t>::min()) || |
| // (!neg && val > std::numeric_limits<int32_t>::max())) { |
| // return false; |
| // } |
| // } |
| // } |
| // |
| // if (neg) val = -val; |
| // |
| // while (i < len && isspace16(s[i])) { |
| // i++; |
| // } |
| // |
| // if (i != len) { |
| // return false; |
| // } |
| // |
| // if (outValue) { |
| // outValue->dataType = |
| // isHex ? outValue->TYPE_INT_HEX : outValue->TYPE_INT_DEC; |
| // outValue->data = static_cast<Res_value::data_type>(val); |
| // } |
| // return true; |
| //} |
| // |
| //bool ResTable::stringToInt(const char16_t* s, size_t len, Res_value* outValue) |
| //{ |
| // return U16StringToInt(s, len, outValue); |
| //} |
| // |
| //bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue) |
| //{ |
| // while (len > 0 && isspace16(*s)) { |
| // s++; |
| // len--; |
| // } |
| // |
| // if (len <= 0) { |
| // return false; |
| // } |
| // |
| // char buf[128]; |
| // int i=0; |
| // while (len > 0 && *s != 0 && i < 126) { |
| // if (*s > 255) { |
| // return false; |
| // } |
| // buf[i++] = *s++; |
| // len--; |
| // } |
| // |
| // if (len > 0) { |
| // return false; |
| // } |
| // if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') { |
| // return false; |
| // } |
| // |
| // buf[i] = 0; |
| // const char* end; |
| // float f = strtof(buf, (char**)&end); |
| // |
| // if (*end != 0 && !isspace((unsigned char)*end)) { |
| // // Might be a unit... |
| // float scale; |
| // if (parse_unit(end, outValue, &scale, &end)) { |
| // f *= scale; |
| // const bool neg = f < 0; |
| // if (neg) f = -f; |
| // uint64_t bits = (uint64_t)(f*(1<<23)+.5f); |
| // uint32_t radix; |
| // uint32_t shift; |
| // if ((bits&0x7fffff) == 0) { |
| // // Always use 23p0 if there is no fraction, just to make |
| // // things easier to read. |
| // radix = Res_value::COMPLEX_RADIX_23p0; |
| // shift = 23; |
| // } else if ((bits&0xffffffffff800000LL) == 0) { |
| // // Magnitude is zero -- can fit in 0 bits of precision. |
| // radix = Res_value::COMPLEX_RADIX_0p23; |
| // shift = 0; |
| // } else if ((bits&0xffffffff80000000LL) == 0) { |
| // // Magnitude can fit in 8 bits of precision. |
| // radix = Res_value::COMPLEX_RADIX_8p15; |
| // shift = 8; |
| // } else if ((bits&0xffffff8000000000LL) == 0) { |
| // // Magnitude can fit in 16 bits of precision. |
| // radix = Res_value::COMPLEX_RADIX_16p7; |
| // shift = 16; |
| // } else { |
| // // Magnitude needs entire range, so no fractional part. |
| // radix = Res_value::COMPLEX_RADIX_23p0; |
| // shift = 23; |
| // } |
| // int32_t mantissa = (int32_t)( |
| // (bits>>shift) & Res_value::COMPLEX_MANTISSA_MASK); |
| // if (neg) { |
| // mantissa = (-mantissa) & Res_value::COMPLEX_MANTISSA_MASK; |
| // } |
| // outValue->data |= |
| // (radix<<Res_value::COMPLEX_RADIX_SHIFT) |
| // | (mantissa<<Res_value::COMPLEX_MANTISSA_SHIFT); |
| // //printf("Input value: %f 0x%016Lx, mult: %f, radix: %d, shift: %d, final: 0x%08x\n", |
| // // f * (neg ? -1 : 1), bits, f*(1<<23), |
| // // radix, shift, outValue->data); |
| // return true; |
| // } |
| // return false; |
| // } |
| // |
| // while (*end != 0 && isspace((unsigned char)*end)) { |
| // end++; |
| // } |
| // |
| // if (*end == 0) { |
| // if (outValue) { |
| // outValue->dataType = outValue->TYPE_FLOAT; |
| // *(float*)(&outValue->data) = f; |
| // return true; |
| // } |
| // } |
| // |
| // return false; |
| //} |
| // |
| //bool ResTable::stringToValue(Res_value* outValue, String16* outString, |
| // const char16_t* s, size_t len, |
| // bool preserveSpaces, bool coerceType, |
| // uint32_t attrID, |
| // const String16* defType, |
| // const String16* defPackage, |
| // Accessor* accessor, |
| // void* accessorCookie, |
| // uint32_t attrType, |
| // bool enforcePrivate) const |
| //{ |
| // bool localizationSetting = accessor != NULL && accessor->getLocalizationSetting(); |
| // const char* errorMsg = NULL; |
| // |
| // outValue->size = sizeof(Res_value); |
| // outValue->res0 = 0; |
| // |
| // // First strip leading/trailing whitespace. Do this before handling |
| // // escapes, so they can be used to force whitespace into the string. |
| // if (!preserveSpaces) { |
| // while (len > 0 && isspace16(*s)) { |
| // s++; |
| // len--; |
| // } |
| // while (len > 0 && isspace16(s[len-1])) { |
| // len--; |
| // } |
| // // If the string ends with '\', then we keep the space after it. |
| // if (len > 0 && s[len-1] == '\\' && s[len] != 0) { |
| // len++; |
| // } |
| // } |
| // |
| // //printf("Value for: %s\n", String8(s, len).string()); |
| // |
| // uint32_t l10nReq = ResTable_map::L10N_NOT_REQUIRED; |
| // uint32_t attrMin = 0x80000000, attrMax = 0x7fffffff; |
| // bool fromAccessor = false; |
| // if (attrID != 0 && !Res_INTERNALID(attrID)) { |
| // const ssize_t p = getResourcePackageIndex(attrID); |
| // const bag_entry* bag; |
| // ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1; |
| // //printf("For attr 0x%08x got bag of %d\n", attrID, cnt); |
| // if (cnt >= 0) { |
| // while (cnt > 0) { |
| // //printf("Entry 0x%08x = 0x%08x\n", bag->map.name.ident, bag->map.value.data); |
| // switch (bag->map.name.ident) { |
| // case ResTable_map::ATTR_TYPE: |
| // attrType = bag->map.value.data; |
| // break; |
| // case ResTable_map::ATTR_MIN: |
| // attrMin = bag->map.value.data; |
| // break; |
| // case ResTable_map::ATTR_MAX: |
| // attrMax = bag->map.value.data; |
| // break; |
| // case ResTable_map::ATTR_L10N: |
| // l10nReq = bag->map.value.data; |
| // break; |
| // } |
| // bag++; |
| // cnt--; |
| // } |
| // unlockBag(bag); |
| // } else if (accessor && accessor->getAttributeType(attrID, &attrType)) { |
| // fromAccessor = true; |
| // if (attrType == ResTable_map::TYPE_ENUM |
| // || attrType == ResTable_map::TYPE_FLAGS |
| // || attrType == ResTable_map::TYPE_INTEGER) { |
| // accessor->getAttributeMin(attrID, &attrMin); |
| // accessor->getAttributeMax(attrID, &attrMax); |
| // } |
| // if (localizationSetting) { |
| // l10nReq = accessor->getAttributeL10N(attrID); |
| // } |
| // } |
| // } |
| // |
| // const bool canStringCoerce = |
| // coerceType && (attrType&ResTable_map::TYPE_STRING) != 0; |
| // |
| // if (*s == '@') { |
| // outValue->dataType = outValue->TYPE_REFERENCE; |
| // |
| // // Note: we don't check attrType here because the reference can |
| // // be to any other type; we just need to count on the client making |
| // // sure the referenced type is correct. |
| // |
| // //printf("Looking up ref: %s\n", String8(s, len).string()); |
| // |
| // // It's a reference! |
| // if (len == 5 && s[1]=='n' && s[2]=='u' && s[3]=='l' && s[4]=='l') { |
| // // Special case @null as undefined. This will be converted by |
| // // AssetManager to TYPE_NULL with data DATA_NULL_UNDEFINED. |
| // outValue->data = 0; |
| // return true; |
| // } else if (len == 6 && s[1]=='e' && s[2]=='m' && s[3]=='p' && s[4]=='t' && s[5]=='y') { |
| // // Special case @empty as explicitly defined empty value. |
| // outValue->dataType = Res_value::TYPE_NULL; |
| // outValue->data = Res_value::DATA_NULL_EMPTY; |
| // return true; |
| // } else { |
| // bool createIfNotFound = false; |
| // const char16_t* resourceRefName; |
| // int resourceNameLen; |
| // if (len > 2 && s[1] == '+') { |
| // createIfNotFound = true; |
| // resourceRefName = s + 2; |
| // resourceNameLen = len - 2; |
| // } else if (len > 2 && s[1] == '*') { |
| // enforcePrivate = false; |
| // resourceRefName = s + 2; |
| // resourceNameLen = len - 2; |
| // } else { |
| // createIfNotFound = false; |
| // resourceRefName = s + 1; |
| // resourceNameLen = len - 1; |
| // } |
| // String16 package, type, name; |
| // if (!expandResourceRef(resourceRefName,resourceNameLen, &package, &type, &name, |
| // defType, defPackage, &errorMsg)) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, errorMsg); |
| // } |
| // return false; |
| // } |
| // |
| // uint32_t specFlags = 0; |
| // uint32_t rid = identifierForName(name.string(), name.size(), type.string(), |
| // type.size(), package.string(), package.size(), &specFlags); |
| // if (rid != 0) { |
| // if (enforcePrivate) { |
| // if (accessor == NULL || accessor->getAssetsPackage() != package) { |
| // if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Resource is not public."); |
| // } |
| // return false; |
| // } |
| // } |
| // } |
| // |
| // if (accessor) { |
| // rid = Res_MAKEID( |
| // accessor->getRemappedPackage(Res_GETPACKAGE(rid)), |
| // Res_GETTYPE(rid), Res_GETENTRY(rid)); |
| // if (kDebugTableNoisy) { |
| // ALOGI("Incl %s:%s/%s: 0x%08x\n", |
| // String8(package).string(), String8(type).string(), |
| // String8(name).string(), rid); |
| // } |
| // } |
| // |
| // uint32_t packageId = Res_GETPACKAGE(rid) + 1; |
| // if (packageId != APP_PACKAGE_ID && packageId != SYS_PACKAGE_ID) { |
| // outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE; |
| // } |
| // outValue->data = rid; |
| // return true; |
| // } |
| // |
| // if (accessor) { |
| // uint32_t rid = accessor->getCustomResourceWithCreation(package, type, name, |
| // createIfNotFound); |
| // if (rid != 0) { |
| // if (kDebugTableNoisy) { |
| // ALOGI("Pckg %s:%s/%s: 0x%08x\n", |
| // String8(package).string(), String8(type).string(), |
| // String8(name).string(), rid); |
| // } |
| // uint32_t packageId = Res_GETPACKAGE(rid) + 1; |
| // if (packageId == 0x00) { |
| // outValue->data = rid; |
| // outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE; |
| // return true; |
| // } else if (packageId == APP_PACKAGE_ID || packageId == SYS_PACKAGE_ID) { |
| // // We accept packageId's generated as 0x01 in order to support |
| // // building the android system resources |
| // outValue->data = rid; |
| // return true; |
| // } |
| // } |
| // } |
| // } |
| // |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "No resource found that matches the given name"); |
| // } |
| // return false; |
| // } |
| // |
| // // if we got to here, and localization is required and it's not a reference, |
| // // complain and bail. |
| // if (l10nReq == ResTable_map::L10N_SUGGESTED) { |
| // if (localizationSetting) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "This attribute must be localized."); |
| // } |
| // } |
| // } |
| // |
| // if (*s == '#') { |
| // // It's a color! Convert to an integer of the form 0xaarrggbb. |
| // uint32_t color = 0; |
| // bool error = false; |
| // if (len == 4) { |
| // outValue->dataType = outValue->TYPE_INT_COLOR_RGB4; |
| // color |= 0xFF000000; |
| // color |= get_hex(s[1], &error) << 20; |
| // color |= get_hex(s[1], &error) << 16; |
| // color |= get_hex(s[2], &error) << 12; |
| // color |= get_hex(s[2], &error) << 8; |
| // color |= get_hex(s[3], &error) << 4; |
| // color |= get_hex(s[3], &error); |
| // } else if (len == 5) { |
| // outValue->dataType = outValue->TYPE_INT_COLOR_ARGB4; |
| // color |= get_hex(s[1], &error) << 28; |
| // color |= get_hex(s[1], &error) << 24; |
| // color |= get_hex(s[2], &error) << 20; |
| // color |= get_hex(s[2], &error) << 16; |
| // color |= get_hex(s[3], &error) << 12; |
| // color |= get_hex(s[3], &error) << 8; |
| // color |= get_hex(s[4], &error) << 4; |
| // color |= get_hex(s[4], &error); |
| // } else if (len == 7) { |
| // outValue->dataType = outValue->TYPE_INT_COLOR_RGB8; |
| // color |= 0xFF000000; |
| // color |= get_hex(s[1], &error) << 20; |
| // color |= get_hex(s[2], &error) << 16; |
| // color |= get_hex(s[3], &error) << 12; |
| // color |= get_hex(s[4], &error) << 8; |
| // color |= get_hex(s[5], &error) << 4; |
| // color |= get_hex(s[6], &error); |
| // } else if (len == 9) { |
| // outValue->dataType = outValue->TYPE_INT_COLOR_ARGB8; |
| // color |= get_hex(s[1], &error) << 28; |
| // color |= get_hex(s[2], &error) << 24; |
| // color |= get_hex(s[3], &error) << 20; |
| // color |= get_hex(s[4], &error) << 16; |
| // color |= get_hex(s[5], &error) << 12; |
| // color |= get_hex(s[6], &error) << 8; |
| // color |= get_hex(s[7], &error) << 4; |
| // color |= get_hex(s[8], &error); |
| // } else { |
| // error = true; |
| // } |
| // if (!error) { |
| // if ((attrType&ResTable_map::TYPE_COLOR) == 0) { |
| // if (!canStringCoerce) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, |
| // "Color types not allowed"); |
| // } |
| // return false; |
| // } |
| // } else { |
| // outValue->data = color; |
| // //printf("Color input=%s, output=0x%x\n", String8(s, len).string(), color); |
| // return true; |
| // } |
| // } else { |
| // if ((attrType&ResTable_map::TYPE_COLOR) != 0) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Color value not valid --" |
| // " must be #rgb, #argb, #rrggbb, or #aarrggbb"); |
| // } |
| // #if 0 |
| // fprintf(stderr, "%s: Color ID %s value %s is not valid\n", |
| // "Resource File", //(const char*)in->getPrintableSource(), |
| // String8(*curTag).string(), |
| // String8(s, len).string()); |
| // #endif |
| // return false; |
| // } |
| // } |
| // } |
| // |
| // if (*s == '?') { |
| // outValue->dataType = outValue->TYPE_ATTRIBUTE; |
| // |
| // // Note: we don't check attrType here because the reference can |
| // // be to any other type; we just need to count on the client making |
| // // sure the referenced type is correct. |
| // |
| // //printf("Looking up attr: %s\n", String8(s, len).string()); |
| // |
| // static const String16 attr16("attr"); |
| // String16 package, type, name; |
| // if (!expandResourceRef(s+1, len-1, &package, &type, &name, |
| // &attr16, defPackage, &errorMsg)) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, errorMsg); |
| // } |
| // return false; |
| // } |
| // |
| // //printf("Pkg: %s, Type: %s, Name: %s\n", |
| // // String8(package).string(), String8(type).string(), |
| // // String8(name).string()); |
| // uint32_t specFlags = 0; |
| // uint32_t rid = |
| // identifierForName(name.string(), name.size(), |
| // type.string(), type.size(), |
| // package.string(), package.size(), &specFlags); |
| // if (rid != 0) { |
| // if (enforcePrivate) { |
| // if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Attribute is not public."); |
| // } |
| // return false; |
| // } |
| // } |
| // |
| // if (accessor) { |
| // rid = Res_MAKEID( |
| // accessor->getRemappedPackage(Res_GETPACKAGE(rid)), |
| // Res_GETTYPE(rid), Res_GETENTRY(rid)); |
| // } |
| // |
| // uint32_t packageId = Res_GETPACKAGE(rid) + 1; |
| // if (packageId != APP_PACKAGE_ID && packageId != SYS_PACKAGE_ID) { |
| // outValue->dataType = Res_value::TYPE_DYNAMIC_ATTRIBUTE; |
| // } |
| // outValue->data = rid; |
| // return true; |
| // } |
| // |
| // if (accessor) { |
| // uint32_t rid = accessor->getCustomResource(package, type, name); |
| // if (rid != 0) { |
| // uint32_t packageId = Res_GETPACKAGE(rid) + 1; |
| // if (packageId == 0x00) { |
| // outValue->data = rid; |
| // outValue->dataType = Res_value::TYPE_DYNAMIC_ATTRIBUTE; |
| // return true; |
| // } else if (packageId == APP_PACKAGE_ID || packageId == SYS_PACKAGE_ID) { |
| // // We accept packageId's generated as 0x01 in order to support |
| // // building the android system resources |
| // outValue->data = rid; |
| // return true; |
| // } |
| // } |
| // } |
| // |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "No resource found that matches the given name"); |
| // } |
| // return false; |
| // } |
| // |
| // if (stringToInt(s, len, outValue)) { |
| // if ((attrType&ResTable_map::TYPE_INTEGER) == 0) { |
| // // If this type does not allow integers, but does allow floats, |
| // // fall through on this error case because the float type should |
| // // be able to accept any integer value. |
| // if (!canStringCoerce && (attrType&ResTable_map::TYPE_FLOAT) == 0) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Integer types not allowed"); |
| // } |
| // return false; |
| // } |
| // } else { |
| // if (((int32_t)outValue->data) < ((int32_t)attrMin) |
| // || ((int32_t)outValue->data) > ((int32_t)attrMax)) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Integer value out of range"); |
| // } |
| // return false; |
| // } |
| // return true; |
| // } |
| // } |
| // |
| // if (stringToFloat(s, len, outValue)) { |
| // if (outValue->dataType == Res_value::TYPE_DIMENSION) { |
| // if ((attrType&ResTable_map::TYPE_DIMENSION) != 0) { |
| // return true; |
| // } |
| // if (!canStringCoerce) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Dimension types not allowed"); |
| // } |
| // return false; |
| // } |
| // } else if (outValue->dataType == Res_value::TYPE_FRACTION) { |
| // if ((attrType&ResTable_map::TYPE_FRACTION) != 0) { |
| // return true; |
| // } |
| // if (!canStringCoerce) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Fraction types not allowed"); |
| // } |
| // return false; |
| // } |
| // } else if ((attrType&ResTable_map::TYPE_FLOAT) == 0) { |
| // if (!canStringCoerce) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Float types not allowed"); |
| // } |
| // return false; |
| // } |
| // } else { |
| // return true; |
| // } |
| // } |
| // |
| // if (len == 4) { |
| // if ((s[0] == 't' || s[0] == 'T') && |
| // (s[1] == 'r' || s[1] == 'R') && |
| // (s[2] == 'u' || s[2] == 'U') && |
| // (s[3] == 'e' || s[3] == 'E')) { |
| // if ((attrType&ResTable_map::TYPE_BOOLEAN) == 0) { |
| // if (!canStringCoerce) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Boolean types not allowed"); |
| // } |
| // return false; |
| // } |
| // } else { |
| // outValue->dataType = outValue->TYPE_INT_BOOLEAN; |
| // outValue->data = (uint32_t)-1; |
| // return true; |
| // } |
| // } |
| // } |
| // |
| // if (len == 5) { |
| // if ((s[0] == 'f' || s[0] == 'F') && |
| // (s[1] == 'a' || s[1] == 'A') && |
| // (s[2] == 'l' || s[2] == 'L') && |
| // (s[3] == 's' || s[3] == 'S') && |
| // (s[4] == 'e' || s[4] == 'E')) { |
| // if ((attrType&ResTable_map::TYPE_BOOLEAN) == 0) { |
| // if (!canStringCoerce) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "Boolean types not allowed"); |
| // } |
| // return false; |
| // } |
| // } else { |
| // outValue->dataType = outValue->TYPE_INT_BOOLEAN; |
| // outValue->data = 0; |
| // return true; |
| // } |
| // } |
| // } |
| // |
| // if ((attrType&ResTable_map::TYPE_ENUM) != 0) { |
| // const ssize_t p = getResourcePackageIndex(attrID); |
| // const bag_entry* bag; |
| // ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1; |
| // //printf("Got %d for enum\n", cnt); |
| // if (cnt >= 0) { |
| // resource_name rname; |
| // while (cnt > 0) { |
| // if (!Res_INTERNALID(bag->map.name.ident)) { |
| // //printf("Trying attr #%08x\n", bag->map.name.ident); |
| // if (getResourceName(bag->map.name.ident, false, &rname)) { |
| // #if 0 |
| // printf("Matching %s against %s (0x%08x)\n", |
| // String8(s, len).string(), |
| // String8(rname.name, rname.nameLen).string(), |
| // bag->map.name.ident); |
| // #endif |
| // if (strzcmp16(s, len, rname.name, rname.nameLen) == 0) { |
| // outValue->dataType = bag->map.value.dataType; |
| // outValue->data = bag->map.value.data; |
| // unlockBag(bag); |
| // return true; |
| // } |
| // } |
| // |
| // } |
| // bag++; |
| // cnt--; |
| // } |
| // unlockBag(bag); |
| // } |
| // |
| // if (fromAccessor) { |
| // if (accessor->getAttributeEnum(attrID, s, len, outValue)) { |
| // return true; |
| // } |
| // } |
| // } |
| // |
| // if ((attrType&ResTable_map::TYPE_FLAGS) != 0) { |
| // const ssize_t p = getResourcePackageIndex(attrID); |
| // const bag_entry* bag; |
| // ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1; |
| // //printf("Got %d for flags\n", cnt); |
| // if (cnt >= 0) { |
| // bool failed = false; |
| // resource_name rname; |
| // outValue->dataType = Res_value::TYPE_INT_HEX; |
| // outValue->data = 0; |
| // const char16_t* end = s + len; |
| // const char16_t* pos = s; |
| // while (pos < end && !failed) { |
| // const char16_t* start = pos; |
| // pos++; |
| // while (pos < end && *pos != '|') { |
| // pos++; |
| // } |
| // //printf("Looking for: %s\n", String8(start, pos-start).string()); |
| // const bag_entry* bagi = bag; |
| // ssize_t i; |
| // for (i=0; i<cnt; i++, bagi++) { |
| // if (!Res_INTERNALID(bagi->map.name.ident)) { |
| // //printf("Trying attr #%08x\n", bagi->map.name.ident); |
| // if (getResourceName(bagi->map.name.ident, false, &rname)) { |
| // #if 0 |
| // printf("Matching %s against %s (0x%08x)\n", |
| // String8(start,pos-start).string(), |
| // String8(rname.name, rname.nameLen).string(), |
| // bagi->map.name.ident); |
| // #endif |
| // if (strzcmp16(start, pos-start, rname.name, rname.nameLen) == 0) { |
| // outValue->data |= bagi->map.value.data; |
| // break; |
| // } |
| // } |
| // } |
| // } |
| // if (i >= cnt) { |
| // // Didn't find this flag identifier. |
| // failed = true; |
| // } |
| // if (pos < end) { |
| // pos++; |
| // } |
| // } |
| // unlockBag(bag); |
| // if (!failed) { |
| // //printf("Final flag value: 0x%lx\n", outValue->data); |
| // return true; |
| // } |
| // } |
| // |
| // |
| // if (fromAccessor) { |
| // if (accessor->getAttributeFlags(attrID, s, len, outValue)) { |
| // //printf("Final flag value: 0x%lx\n", outValue->data); |
| // return true; |
| // } |
| // } |
| // } |
| // |
| // if ((attrType&ResTable_map::TYPE_STRING) == 0) { |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, "String types not allowed"); |
| // } |
| // return false; |
| // } |
| // |
| // // Generic string handling... |
| // outValue->dataType = outValue->TYPE_STRING; |
| // if (outString) { |
| // bool failed = collectString(outString, s, len, preserveSpaces, &errorMsg); |
| // if (accessor != NULL) { |
| // accessor->reportError(accessorCookie, errorMsg); |
| // } |
| // return failed; |
| // } |
| // |
| // return true; |
| //} |
| // |
| //bool ResTable::collectString(String16* outString, |
| // const char16_t* s, size_t len, |
| // bool preserveSpaces, |
| // const char** outErrorMsg, |
| // bool append) |
| //{ |
| // String16 tmp; |
| // |
| // char quoted = 0; |
| // const char16_t* p = s; |
| // while (p < (s+len)) { |
| // while (p < (s+len)) { |
| // const char16_t c = *p; |
| // if (c == '\\') { |
| // break; |
| // } |
| // if (!preserveSpaces) { |
| // if (quoted == 0 && isspace16(c) |
| // && (c != ' ' || isspace16(*(p+1)))) { |
| // break; |
| // } |
| // if (c == '"' && (quoted == 0 || quoted == '"')) { |
| // break; |
| // } |
| // if (c == '\'' && (quoted == 0 || quoted == '\'')) { |
| // /* |
| // * In practice, when people write ' instead of \' |
| // * in a string, they are doing it by accident |
| // * instead of really meaning to use ' as a quoting |
| // * character. Warn them so they don't lose it. |
| // */ |
| // if (outErrorMsg) { |
| // *outErrorMsg = "Apostrophe not preceded by \\"; |
| // } |
| // return false; |
| // } |
| // } |
| // p++; |
| // } |
| // if (p < (s+len)) { |
| // if (p > s) { |
| // tmp.append(String16(s, p-s)); |
| // } |
| // if (!preserveSpaces && (*p == '"' || *p == '\'')) { |
| // if (quoted == 0) { |
| // quoted = *p; |
| // } else { |
| // quoted = 0; |
| // } |
| // p++; |
| // } else if (!preserveSpaces && isspace16(*p)) { |
| // // Space outside of a quote -- consume all spaces and |
| // // leave a single plain space char. |
| // tmp.append(String16(" ")); |
| // p++; |
| // while (p < (s+len) && isspace16(*p)) { |
| // p++; |
| // } |
| // } else if (*p == '\\') { |
| // p++; |
| // if (p < (s+len)) { |
| // switch (*p) { |
| // case 't': |
| // tmp.append(String16("\t")); |
| // break; |
| // case 'n': |
| // tmp.append(String16("\n")); |
| // break; |
| // case '#': |
| // tmp.append(String16("#")); |
| // break; |
| // case '@': |
| // tmp.append(String16("@")); |
| // break; |
| // case '?': |
| // tmp.append(String16("?")); |
| // break; |
| // case '"': |
| // tmp.append(String16("\"")); |
| // break; |
| // case '\'': |
| // tmp.append(String16("'")); |
| // break; |
| // case '\\': |
| // tmp.append(String16("\\")); |
| // break; |
| // case 'u': |
| // { |
| // char16_t chr = 0; |
| // int i = 0; |
| // while (i < 4 && p[1] != 0) { |
| // p++; |
| // i++; |
| // int c; |
| // if (*p >= '0' && *p <= '9') { |
| // c = *p - '0'; |
| // } else if (*p >= 'a' && *p <= 'f') { |
| // c = *p - 'a' + 10; |
| // } else if (*p >= 'A' && *p <= 'F') { |
| // c = *p - 'A' + 10; |
| // } else { |
| // if (outErrorMsg) { |
| // *outErrorMsg = "Bad character in \\u unicode escape sequence"; |
| // } |
| // return false; |
| // } |
| // chr = (chr<<4) | c; |
| // } |
| // tmp.append(String16(&chr, 1)); |
| // } break; |
| // default: |
| // // ignore unknown escape chars. |
| // break; |
| // } |
| // p++; |
| // } |
| // } |
| // len -= (p-s); |
| // s = p; |
| // } |
| // } |
| // |
| // if (tmp.size() != 0) { |
| // if (len > 0) { |
| // tmp.append(String16(s, len)); |
| // } |
| // if (append) { |
| // outString->append(tmp); |
| // } else { |
| // outString->setTo(tmp); |
| // } |
| // } else { |
| // if (append) { |
| // outString->append(String16(s, len)); |
| // } else { |
| // outString->setTo(s, len); |
| // } |
| // } |
| // |
| // return true; |
| //} |
| |
| public int getBasePackageCount() |
| { |
| if (mError != NO_ERROR) { |
| return 0; |
| } |
| return mPackageGroups.size(); |
| } |
| |
| public String getBasePackageName(int idx) |
| { |
| if (mError != NO_ERROR) { |
| return null; |
| } |
| LOG_FATAL_IF(idx >= mPackageGroups.size(), |
| "Requested package index %d past package count %d", |
| (int)idx, (int)mPackageGroups.size()); |
| return mPackageGroups.get(keyFor(idx)).name; |
| } |
| |
| public int getBasePackageId(int idx) |
| { |
| if (mError != NO_ERROR) { |
| return 0; |
| } |
| LOG_FATAL_IF(idx >= mPackageGroups.size(), |
| "Requested package index %d past package count %d", |
| (int)idx, (int)mPackageGroups.size()); |
| return mPackageGroups.get(keyFor(idx)).id; |
| } |
| |
| int getLastTypeIdForPackage(int idx) |
| { |
| if (mError != NO_ERROR) { |
| return 0; |
| } |
| LOG_FATAL_IF(idx >= mPackageGroups.size(), |
| "Requested package index %d past package count %d", |
| (int)idx, (int)mPackageGroups.size()); |
| PackageGroup group = mPackageGroups.get(keyFor(idx)); |
| return group.largestTypeId; |
| } |
| |
| int keyFor(int idx) { |
| ArrayList<Integer> keys = new ArrayList<>(mPackageGroups.keySet()); |
| Collections.sort(keys); |
| return keys.get(idx); |
| } |
| |
| public int getTableCount() { |
| return mHeaders.size(); |
| } |
| |
| public ResStringPool getTableStringBlock(int index) { |
| return mHeaders.get(index).values; |
| } |
| |
| public DynamicRefTable getDynamicRefTableForCookie(int cookie) { |
| for (PackageGroup pg : mPackageGroups.values()) { |
| int M = pg.packages.size(); |
| for (int j = 0; j < M; j++) { |
| if (pg.packages.get(j).header.cookie == cookie) { |
| return pg.dynamicRefTable; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public boolean getResourceName(int resID, boolean allowUtf8, ResourceName outName) { |
| if (mError != NO_ERROR) { |
| return false; |
| } |
| |
| final int p = getResourcePackageIndex(resID); |
| final int t = Res_GETTYPE(resID); |
| final int e = Res_GETENTRY(resID); |
| |
| if (p < 0) { |
| if (Res_GETPACKAGE(resID)+1 == 0) { |
| ALOGW("No package identifier when getting name for resource number 0x%08x", resID); |
| } |
| return false; |
| } |
| if (t < 0) { |
| ALOGW("No type identifier when getting name for resource number 0x%08x", resID); |
| return false; |
| } |
| |
| final PackageGroup grp = mPackageGroups.get(p); |
| if (grp == NULL) { |
| ALOGW("Bad identifier when getting name for resource number 0x%08x", resID); |
| return false; |
| } |
| |
| Entry entry = new Entry(); |
| int err = getEntry(grp, t, e, null, entry); |
| if (err != NO_ERROR) { |
| return false; |
| } |
| |
| outName.packageName = grp.name; |
| outName.type = entry.typeStr.string(); |
| if (outName.type == null) { |
| return false; |
| } |
| outName.name = entry.keyStr.string(); |
| if (outName.name == null) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| String getResourceName(int resId) { |
| ResourceName outName = new ResourceName(); |
| if (getResourceName(resId, true, outName)) { |
| return outName.toString(); |
| } |
| throw new IllegalArgumentException("Unknown resource id " + resId); |
| } |
| |
| // A group of objects describing a particular resource package. |
| // The first in 'package' is always the root object (from the resource |
| // table that defined the package); the ones after are skins on top of it. |
| // from ResourceTypes.cpp struct ResTable::PackageGroup |
| public static class PackageGroup |
| { |
| public PackageGroup( |
| ResTable _owner, final String _name, int _id, |
| boolean appAsLib, boolean _isSystemAsset) |
| // : owner(_owner) |
| // , name(_name) |
| // , id(_id) |
| // , largestTypeId(0) |
| // , dynamicRefTable(static_cast<uint8_t>(_id), appAsLib) |
| // , isSystemAsset(_isSystemAsset) |
| { |
| this.owner = _owner; |
| this.name = _name; |
| this.id = _id; |
| this.dynamicRefTable = new DynamicRefTable((byte) _id, appAsLib); |
| this.isSystemAsset = _isSystemAsset; |
| } |
| |
| // ~PackageGroup() { |
| // clearBagCache(); |
| // final int numTypes = types.size(); |
| // for (int i = 0; i < numTypes; i++) { |
| // final List<DataType> typeList = types.get(i); |
| // final int numInnerTypes = typeList.size(); |
| // for (int j = 0; j < numInnerTypes; j++) { |
| // if (typeList.get(j)._package_.owner == owner) { |
| // delete typeList[j]; |
| // } |
| // } |
| // typeList.clear(); |
| // } |
| // |
| // final int N = packages.size(); |
| // for (int i=0; i<N; i++) { |
| // ResTable_package pkg = packages[i]; |
| // if (pkg.owner == owner) { |
| // delete pkg; |
| // } |
| // } |
| // } |
| |
| /** |
| * Clear all cache related data that depends on parameters/configuration. |
| * This includes the bag caches and filtered types. |
| */ |
| void clearBagCache() { |
| // for (int i = 0; i < typeCacheEntries.size(); i++) { |
| // if (kDebugTableNoisy) { |
| // printf("type=0x%x\n", i); |
| // } |
| // final List<DataType> typeList = types.get(i); |
| // if (!typeList.isEmpty()) { |
| // TypeCacheEntry cacheEntry = typeCacheEntries.editItemAt(i); |
| // |
| // // Reset the filtered configurations. |
| // cacheEntry.filteredConfigs.clear(); |
| // |
| // bag_set[][] typeBags = cacheEntry.cachedBags; |
| // if (kDebugTableNoisy) { |
| // printf("typeBags=%s\n", typeBags); |
| // } |
| // |
| // if (isTruthy(typeBags)) { |
| // final int N = typeList.get(0).entryCount; |
| // if (kDebugTableNoisy) { |
| // printf("type.entryCount=0x%x\n", N); |
| // } |
| // for (int j = 0; j < N; j++) { |
| // if (typeBags[j] && typeBags[j] != (bag_set *) 0xFFFFFFFF){ |
| // free(typeBags[j]); |
| // } |
| // } |
| // free(typeBags); |
| // cacheEntry.cachedBags = NULL; |
| // } |
| // } |
| // } |
| } |
| |
| private void printf(String message, Object... arguments) { |
| System.out.print(String.format(message, arguments)); |
| } |
| |
| // long findType16(final String type, int len) { |
| // final int N = packages.size(); |
| // for (int i = 0; i < N; i++) { |
| // sint index = packages[i].typeStrings.indexOfString(type, len); |
| // if (index >= 0) { |
| // return index + packages[i].typeIdOffset; |
| // } |
| // } |
| // return -1; |
| // } |
| |
| final ResTable owner; |
| final String name; |
| final int id; |
| |
| // This is mainly used to keep track of the loaded packages |
| // and to clean them up properly. Accessing resources happens from |
| // the 'types' array. |
| List<Package> packages = new ArrayList<>(); |
| |
| public final Map<Integer, List<Type>> types = new HashMap<>(); |
| |
| byte largestTypeId; |
| |
| // Cached objects dependent on the parameters/configuration of this ResTable. |
| // Gets cleared whenever the parameters/configuration changes. |
| // These are stored here in a parallel structure because the data in `types` may |
| // be shared by other ResTable's (framework resources are shared this way). |
| ByteBucketArray<TypeCacheEntry> typeCacheEntries = new ByteBucketArray<TypeCacheEntry>() { |
| @Override |
| TypeCacheEntry newInstance() { |
| return new TypeCacheEntry(); |
| } |
| }; |
| |
| // The table mapping dynamic references to resolved references for |
| // this package group. |
| // TODO: We may be able to support dynamic references in overlays |
| // by having these tables in a per-package scope rather than |
| // per-package-group. |
| DynamicRefTable dynamicRefTable; |
| |
| // If the package group comes from a system asset. Used in |
| // determining non-system locales. |
| final boolean isSystemAsset; |
| } |
| |
| // -------------------------------------------------------------------- |
| // -------------------------------------------------------------------- |
| // -------------------------------------------------------------------- |
| |
| // struct ResTable::Header |
| public static class Header |
| { |
| // Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL), |
| // resourceIDMap(NULL), resourceIDMapSize(0) { } |
| |
| public Header(ResTable owner) { |
| this.owner = owner; |
| } |
| |
| // ~Header() |
| // { |
| // free(resourceIDMap); |
| // } |
| |
| ResTable owner; |
| byte[] ownedData; |
| ResTable_header header; |
| int size; |
| int dataEnd; |
| int index; |
| int cookie; |
| |
| ResStringPool values = new ResStringPool(); |
| int[] resourceIDMap; |
| int resourceIDMapSize; |
| }; |
| |
| public static class Entry { |
| ResTable_config config; |
| ResTable_entry entry; |
| ResTable_type type; |
| int specFlags; |
| Package _package_; |
| |
| StringPoolRef typeStr; |
| StringPoolRef keyStr; |
| } |
| |
| // struct ResTable::DataType |
| public static class Type { |
| |
| final Header header; |
| final Package _package_; |
| public final int entryCount; |
| public ResTable_typeSpec typeSpec; |
| public int[] typeSpecFlags; |
| public IdmapEntries idmapEntries = new IdmapEntries(); |
| public List<ResTable_type> configs; |
| |
| public Type(final Header _header, final Package _package, int count) |
| // : header(_header), package(_package), entryCount(count), |
| // typeSpec(NULL), typeSpecFlags(NULL) { } |
| { |
| this.header = _header; |
| _package_ = _package; |
| this.entryCount = count; |
| this.typeSpec = null; |
| this.typeSpecFlags = null; |
| this.configs = new ArrayList<>(); |
| } |
| } |
| |
| // struct ResTable::Package |
| public static class Package { |
| // Package(ResTable* _owner, final Header* _header, final ResTable_package* _package) |
| // : owner(_owner), header(_header), package(_package), typeIdOffset(0) { |
| // if (dtohs(package.header.headerSize) == sizeof(package)) { |
| // // The package structure is the same size as the definition. |
| // // This means it contains the typeIdOffset field. |
| // typeIdOffset = package.typeIdOffset; |
| // } |
| |
| public Package(ResTable owner, Header header, ResTable_package _package) { |
| this.owner = owner; |
| this.header = header; |
| this._package_ = _package; |
| } |
| |
| final ResTable owner; |
| final Header header; |
| final ResTable_package _package_; |
| |
| ResStringPool typeStrings = new ResStringPool(); |
| ResStringPool keyStrings = new ResStringPool(); |
| |
| int typeIdOffset; |
| }; |
| |
| public static class bag_entry { |
| public int stringBlock; |
| public ResTable_map map = new ResTable_map(); |
| } |
| |
| public void lock() { |
| try { |
| mLock.acquire(); |
| } catch (InterruptedException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public void unlock() { |
| mLock.release(); |
| } |
| |
| public int lockBag(int resID, Ref<bag_entry[]> outBag) { |
| lock(); |
| |
| int err = getBagLocked(resID, outBag, null); |
| if (err < NO_ERROR) { |
| //printf("*** get failed! unlocking\n"); |
| mLock.release(); |
| } |
| return err; |
| } |
| |
| public int getBagLocked(int resID, Ref<bag_entry[]> outBag, Ref<Integer> outTypeSpecFlags) { |
| if (mError != NO_ERROR) { |
| return mError; |
| } |
| |
| final int p = getResourcePackageIndex(resID); |
| final int t = Res_GETTYPE(resID); |
| final int e = Res_GETENTRY(resID); |
| |
| if (p < 0) { |
| ALOGW("Invalid package identifier when getting bag for resource number 0x%08x", resID); |
| return BAD_INDEX; |
| } |
| if (t < 0) { |
| ALOGW("No type identifier when getting bag for resource number 0x%08x", resID); |
| return BAD_INDEX; |
| } |
| |
| //printf("Get bag: id=0x%08x, p=%d, t=%d\n", resID, p, t); |
| PackageGroup grp = mPackageGroups.get(p); |
| if (grp == NULL) { |
| ALOGW("Bad identifier when getting bag for resource number 0x%08x", resID); |
| return BAD_INDEX; |
| } |
| |
| final List<Type> typeConfigs = getOrDefault(grp.types, t, Collections.emptyList()); |
| if (typeConfigs.isEmpty()) { |
| ALOGW("Type identifier 0x%x does not exist.", t+1); |
| return BAD_INDEX; |
| } |
| |
| final int NENTRY = typeConfigs.get(0).entryCount; |
| if (e >= (int)NENTRY) { |
| ALOGW("Entry identifier 0x%x is larger than entry count 0x%x", |
| e, (int)typeConfigs.get(0).entryCount); |
| return BAD_INDEX; |
| } |
| |
| // First see if we've already computed this bag... |
| TypeCacheEntry cacheEntry = grp.typeCacheEntries.editItemAt(t); |
| bag_set[] typeSet = cacheEntry.cachedBags; |
| // todo cache |
| // if (isTruthy(typeSet)) { |
| // bag_set set = typeSet[e]; |
| // if (isTruthy(set)) { |
| // if (set != (bag_set) 0xFFFFFFFF){ |
| // if (set != SENTINEL_BAG_SET){ |
| // if (outTypeSpecFlags != NULL) { |
| // outTypeSpecFlags.set(set.typeSpecFlags); |
| // } |
| // outBag.set((bag_entry *) (set + 1); |
| // if (kDebugTableSuperNoisy) { |
| // ALOGI("Found existing bag for: 0x%x\n", resID); |
| // } |
| // return set.numAttrs; |
| // } |
| // ALOGW("Attempt to retrieve bag 0x%08x which is invalid or in a cycle.", |
| // resID); |
| // return BAD_INDEX; |
| // } |
| // } |
| // |
| // Bag not found, we need to compute it! |
| if (!isTruthy(typeSet)) { |
| typeSet = new bag_set[NENTRY]; // (bag_set**)calloc(NENTRY, sizeof(bag_set*)); |
| //cacheEntry.cachedBags = typeSet; |
| } |
| // |
| // // Mark that we are currently working on this one. |
| // typeSet[e] = (bag_set*)0xFFFFFFFF; |
| // typeSet[e] = SENTINEL_BAG_SET; |
| |
| if (kDebugTableNoisy) { |
| ALOGI("Building bag: %x\n", resID); |
| } |
| |
| // Now collect all bag attributes |
| Entry entry = new Entry(); |
| int err = getEntry(grp, t, e, mParams, entry); |
| if (err != NO_ERROR) { |
| return err; |
| } |
| final short entrySize = dtohs(entry.entry.size); |
| // const uint32_t parent = entrySize >= sizeof(ResTable_map_entry) |
| // ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0; |
| // const uint32_t count = entrySize >= sizeof(ResTable_map_entry) |
| // ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0; |
| ResTable_map_entry mapEntry = entrySize >= ResTable_map_entry.SIZEOF ? |
| new ResTable_map_entry(entry.entry.myBuf(), entry.entry.myOffset()) : null; |
| final int parent = mapEntry != null ? dtohl(mapEntry.parent.ident) : 0; |
| final int count = mapEntry != null ? dtohl(mapEntry.count) : 0; |
| |
| int N = count; |
| |
| if (kDebugTableNoisy) { |
| ALOGI("Found map: size=%x parent=%x count=%d\n", entrySize, parent, count); |
| |
| // If this map inherits from another, we need to start |
| // with its parent's values. Otherwise start out empty. |
| ALOGI("Creating new bag, entrySize=0x%08x, parent=0x%08x\n", entrySize, parent); |
| } |
| |
| // This is what we are building. |
| bag_set set; |
| |
| if (isTruthy(parent)) { |
| Ref<Integer> resolvedParent = new Ref<>(parent); |
| |
| // Bags encode a parent reference without using the standard |
| // Res_value structure. That means we must always try to |
| // resolve a parent reference in case it is actually a |
| // TYPE_DYNAMIC_REFERENCE. |
| err = grp.dynamicRefTable.lookupResourceId(resolvedParent); |
| if (err != NO_ERROR) { |
| ALOGE("Failed resolving bag parent id 0x%08x", parent); |
| return UNKNOWN_ERROR; |
| } |
| |
| final Ref<bag_entry[]> parentBag = new Ref<>(null); |
| final Ref<Integer> parentTypeSpecFlags = new Ref<>(0); |
| final int NP = getBagLocked(resolvedParent.get(), parentBag, parentTypeSpecFlags); |
| final int NT = ((NP >= 0) ? NP : 0) + N; |
| set = new bag_set(NT); |
| if (NP > 0) { |
| set.copyFrom(parentBag.get(), NP); |
| set.numAttrs = NP; |
| if (kDebugTableNoisy) { |
| ALOGI("Initialized new bag with %d inherited attributes.\n", NP); |
| } |
| } else { |
| if (kDebugTableNoisy) { |
| ALOGI("Initialized new bag with no inherited attributes.\n"); |
| } |
| set.numAttrs = 0; |
| } |
| set.availAttrs = NT; |
| set.typeSpecFlags = parentTypeSpecFlags.get(); |
| } else { |
| set = new bag_set(N); |
| set.numAttrs = 0; |
| set.availAttrs = N; |
| set.typeSpecFlags = 0; |
| } |
| |
| set.typeSpecFlags |= entry.specFlags; |
| |
| // Now merge in the new attributes... |
| // int curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type)) |
| // + dtohs(entry.entry.size); |
| int curOff = entry.entry.myOffset() - entry.type.myOffset() + entry.entry.size; |
| ResTable_map map; |
| // bag_entry* entries = (bag_entry*)(set+1); |
| bag_entry[] entries = set.bag_entries; |
| int curEntry = 0; |
| int pos = 0; |
| if (kDebugTableNoisy) { |
| ALOGI("Starting with set %s, entries=%s, avail=0x%x\n", set, entries, set.availAttrs); |
| } |
| while (pos < count) { |
| if (kDebugTableNoisy) { |
| // ALOGI("Now at %s\n", curOff); |
| ALOGI("Now at %s\n", curEntry); |
| } |
| |
| if (curOff > (dtohl(entry.type.header.size)- ResTable_map.SIZEOF)) { |
| ALOGW("ResTable_map at %d is beyond type chunk data %d", |
| (int)curOff, dtohl(entry.type.header.size)); |
| return BAD_TYPE; |
| } |
| // map = (const ResTable_map*)(((const uint8_t*)entry.type) + curOff); |
| map = new ResTable_map(entry.type.myBuf(), entry.type.myOffset() + curOff); |
| N++; |
| |
| Ref<Integer> newName = new Ref<>(htodl(map.name.ident)); |
| if (!Res_INTERNALID(newName.get())) { |
| // Attributes don't have a resource id as the name. They specify |
| // other data, which would be wrong to change via a lookup. |
| if (grp.dynamicRefTable.lookupResourceId(newName) != NO_ERROR) { |
| ALOGE("Failed resolving ResTable_map name at %d with ident 0x%08x", |
| (int) curEntry, (int) newName.get()); |
| return UNKNOWN_ERROR; |
| } |
| } |
| |
| boolean isInside; |
| int oldName = 0; |
| while ((isInside=(curEntry < set.numAttrs)) |
| && (oldName=entries[curEntry].map.name.ident) < newName.get()) { |
| if (kDebugTableNoisy) { |
| ALOGI("#0x%x: Keeping existing attribute: 0x%08x\n", |
| curEntry, entries[curEntry].map.name.ident); |
| } |
| curEntry++; |
| } |
| |
| if ((!isInside) || oldName != newName.get()) { |
| // This is a new attribute... figure out what to do with it. |
| if (set.numAttrs >= set.availAttrs) { |
| // Need to alloc more memory... |
| final int newAvail = set.availAttrs+N; |
| // set = (bag_set[])realloc(set, |
| // sizeof(bag_set) |
| // + sizeof(bag_entry)*newAvail); |
| set.resizeBagEntries(newAvail); |
| set.availAttrs = newAvail; |
| // entries = (bag_entry*)(set+1); |
| entries = set.bag_entries; |
| if (kDebugTableNoisy) { |
| ALOGI("Reallocated set %s, entries=%s, avail=0x%x\n", |
| set, entries, set.availAttrs); |
| } |
| } |
| if (isInside) { |
| // Going in the middle, need to make space. |
| // memmove(entries+curEntry+1, entries+curEntry, |
| // sizeof(bag_entry)*(set.numAttrs-curEntry)); |
| System.arraycopy(entries, curEntry, entries, curEntry + 1, set.numAttrs - curEntry); |
| entries[curEntry] = null; |
| set.numAttrs++; |
| } |
| if (kDebugTableNoisy) { |
| ALOGI("#0x%x: Inserting new attribute: 0x%08x\n", curEntry, newName.get()); |
| } |
| } else { |
| if (kDebugTableNoisy) { |
| ALOGI("#0x%x: Replacing existing attribute: 0x%08x\n", curEntry, oldName); |
| } |
| } |
| |
| bag_entry cur = entries[curEntry]; |
| if (cur == null) { |
| cur = entries[curEntry] = new bag_entry(); |
| } |
| |
| cur.stringBlock = entry._package_.header.index; |
| cur.map.name.ident = newName.get(); |
| // cur->map.value.copyFrom_dtoh(map->value); |
| cur.map.value = map.value; |
| Ref<Res_value> valueRef = new Ref<>(cur.map.value); |
| err = grp.dynamicRefTable.lookupResourceValue(valueRef); |
| cur.map.value = map.value = valueRef.get(); |
| if (err != NO_ERROR) { |
| ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur.map.value.data); |
| return UNKNOWN_ERROR; |
| } |
| |
| if (kDebugTableNoisy) { |
| ALOGI("Setting entry #0x%x %s: block=%d, name=0x%08d, type=%d, data=0x%08x\n", |
| curEntry, cur, cur.stringBlock, cur.map.name.ident, |
| cur.map.value.dataType, cur.map.value.data); |
| } |
| |
| // On to the next! |
| curEntry++; |
| pos++; |
| final int size = dtohs(map.value.size); |
| // curOff += size + sizeof(*map)-sizeof(map->value); |
| curOff += size + ResTable_map.SIZEOF-Res_value.SIZEOF; |
| }; |
| |
| if (curEntry > set.numAttrs) { |
| set.numAttrs = curEntry; |
| } |
| |
| // And this is it... |
| typeSet[e] = set; |
| if (isTruthy(set)) { |
| if (outTypeSpecFlags != NULL) { |
| outTypeSpecFlags.set(set.typeSpecFlags); |
| } |
| outBag.set(set.bag_entries); |
| if (kDebugTableNoisy) { |
| ALOGI("Returning 0x%x attrs\n", set.numAttrs); |
| } |
| return set.numAttrs; |
| } |
| return BAD_INDEX; |
| } |
| |
| public void unlockBag(Ref<bag_entry[]> bag) { |
| unlock(); |
| } |
| |
| static class bag_set { |
| int numAttrs; // number in array |
| int availAttrs; // total space in array |
| int typeSpecFlags; |
| // Followed by 'numAttr' bag_entry structures. |
| |
| bag_entry[] bag_entries; |
| |
| public bag_set(int entryCount) { |
| bag_entries = new bag_entry[entryCount]; |
| } |
| |
| public void copyFrom(bag_entry[] parentBag, int count) { |
| for (int i = 0; i < count; i++) { |
| bag_entries[i] = parentBag[i]; |
| } |
| } |
| |
| public void resizeBagEntries(int newEntryCount) { |
| bag_entry[] newEntries = new bag_entry[newEntryCount]; |
| System.arraycopy(bag_entries, 0, newEntries, 0, Math.min(bag_entries.length, newEntryCount)); |
| bag_entries = newEntries; |
| } |
| }; |
| |
| /** |
| * Configuration dependent cached data. This must be cleared when the configuration is |
| * changed (setParameters). |
| */ |
| static class TypeCacheEntry { |
| // TypeCacheEntry() : cachedBags(NULL) {} |
| |
| // Computed attribute bags for this type. |
| // bag_set** cachedBags; |
| bag_set[] cachedBags; |
| |
| // Pre-filtered list of configurations (per asset path) that match the parameters set on this |
| // ResTable. |
| List<List<ResTable_type>> filteredConfigs; |
| }; |
| |
| |
| private int Res_MAKEID(int packageId, int typeId, int entryId) { |
| return (((packageId+1)<<24) | (((typeId+1)&0xFF)<<16) | (entryId&0xFFFF)); |
| } |
| |
| // struct resource_name |
| public static class ResourceName { |
| public String packageName; |
| public String type; |
| public String name; |
| |
| @Override |
| public String toString() { |
| return packageName.trim() + '@' + type + ':' + name; |
| } |
| } |
| |
| private interface Function<K, V> { |
| V apply(K key); |
| } |
| |
| static <K, V> V computeIfAbsent(Map<K, V> map, K key, Function<K, V> vFunction) { |
| V v = map.get(key); |
| if (v == null) { |
| v = vFunction.apply(key); |
| map.put(key, v); |
| } |
| return v; |
| } |
| |
| static <K, V> V getOrDefault(Map<K, V> map, K key, V defaultValue) { |
| V v; |
| return (((v = map.get(key)) != null) || map.containsKey(key)) ? v : defaultValue; |
| } |
| } |