Open XML and parse binary XML resource files.
diff --git a/resources/src/main/java/org/robolectric/res/android/Asset.java b/resources/src/main/java/org/robolectric/res/android/Asset.java
index a417295..e03e4c7 100644
--- a/resources/src/main/java/org/robolectric/res/android/Asset.java
+++ b/resources/src/main/java/org/robolectric/res/android/Asset.java
@@ -4,133 +4,28 @@
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.LinkedList;
import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import org.robolectric.res.android.ZipFileRO.ZipEntryRO;
-/**
- * Class providing access to a read-only asset. Asset objects are NOT thread-safe, and should not
- * be shared across threads.
- *
- * transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/include/androidfw/Asset.h
- * and https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/libs/androidfw/Asset.cpp
- *
- * Instances of this class provide read-only operations on a byte stream.
- * Access may
- * be optimized for streaming, random, or whole buffer modes. All operations are supported
- * regardless of how the file was opened, but some things will be less efficient. [pass that
- * in??]. "Asset" is the base class for all types of assets. The classes below
- * provide most of the implementation. The AssetManager uses one of the static "create"
- * functions defined here to create a new instance.
- */
-public class Asset {
+public abstract class Asset {
- private final RandomAccessFile f;
-
- public Asset(RandomAccessFile f) {
- this.f = f;
- }
-
- public long size() {
- try {
- return f.length();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public int read() {
- try {
- return f.read();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public int read(byte[] b, int off, int len) {
- try {
- return f.read(b, off, len);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public long seek(long offset, int whence) {
- throw new UnsupportedOperationException("not yet implemented");
-// if (whence > 0) {
-// SEEK_END
-// } else if (whence < 0) {
-// SEEK_SET
-// } else {
-// SEEK_CUR
-// }
-// return f.seek();
- }
- //
-
- // public:
-// virtual ~Asset(void) = default;
-// static int32_t getGlobalCount();
-// static String8 getAssetAllocations();
-//
-// /* used when opening an asset */
- public enum AccessMode {
- ACCESS_UNKNOWN,
- /* read chunks, and seek forward and backward */
- ACCESS_RANDOM,
- /* read sequentially, with an occasional forward seek */
- ACCESS_STREAMING,
- /* caller plans to ask for a read-only buffer with all data */
- ACCESS_BUFFER,
- }
-
-//using namespace android;
+ //using namespace android;
//#ifndef O_BINARY
//# define O_BINARY 0
//#endif
private static final boolean kIsDebug = false;
-private static final Object gAssetLock = new Object();
-private static List<Asset> gAssets = new LinkedList<>();
-
+ private static final Object gAssetLock = new Object();
+ private static List<Asset> gAssets = new LinkedList<>();
+ public Runnable onClose;
private String8 mAssetSource; // debug string
- /* set the asset source string */
- void setAssetSource(String8 path) {
- mAssetSource = path;
- }
-
-protected void registerAsset(Asset asset)
-{
- int gCount = 0;
- synchronized (gAssetLock) {
- gAssets.add(asset);
- gCount = gAssets.size();
- }
- if (kIsDebug) {
- ALOGI("Creating Asset %p #%d\n", asset, gCount);
- }
-
-}
-
-protected void unregisterAsset(Asset asset) {
- int gCount = 0;
- synchronized (gAssetLock) {
- gAssets.remove(asset);
- gCount = gAssets.size();
- }
- if (kIsDebug) {
- ALOGI("Destroying Asset in %p #%d\n", asset, gCount);
- }
-}
-
-protected int getGlobalCount()
-{
- synchronized (gAssetLock) {
- return gAssets.size();
- }
-}
-
-//String8 Asset::getAssetAllocations()
+ //String8 Asset::getAssetAllocations()
//{
// AutoMutex _l(gAssetLock);
// String8 res;
@@ -202,662 +97,511 @@
// return pAsset;
try {
RandomAccessFile f = new RandomAccessFile(fileName, "r");
- return new Asset(f);
+ return new FileAsset(f);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
-/*
- * Create a new Asset from a compressed file on disk. There is a fair chance
- * that the file doesn't actually exist.
- *
- * We currently support gzip files. We might want to handle .bz2 someday.
- */
-
-static Asset createFromCompressedFile(String fileName,
- AccessMode mode)
-{
- return null;
-// _CompressedAsset pAsset;
-// status_t result;
-// off64_t fileLen;
-// bool scanResult;
-// long offset;
-// int method;
-// long uncompressedLen, compressedLen;
-// int fd;
-// fd = open(fileName, O_RDONLY | O_BINARY);
-// if (fd < 0)
-// return NULL;
-// fileLen = lseek(fd, 0, SEEK_END);
-// if (fileLen < 0) {
-// ::close(fd);
-// return NULL;
-// }
-// (void) lseek(fd, 0, SEEK_SET);
-// /* want buffered I/O for the file scan; must dup so fclose() is safe */
-// FILE* fp = fdopen(dup(fd), "rb");
-// if (fp == NULL) {
-// ::close(fd);
-// return NULL;
-// }
-// unsigned long crc32;
-// scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
-// &compressedLen, &crc32);
-// offset = ftell(fp);
-// fclose(fp);
-// if (!scanResult) {
-// ALOGD("File '%s' is not in gzip format\n", fileName);
-// ::close(fd);
-// return NULL;
-// }
-// pAsset = new _CompressedAsset;
-// result = pAsset->openChunk(fd, offset, method, uncompressedLen,
-// compressedLen);
-// if (result != NO_ERROR) {
-// delete pAsset;
-// return NULL;
-// }
-// pAsset->mAccessMode = mode;
-// return pAsset;
-}
-//#if 0
-///*
-// * Create a new Asset from part of an open file.
-// */
-///*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
-// size_t length, AccessMode mode)
-//{
-// _FileAsset* pAsset;
-// status_t result;
-// pAsset = new _FileAsset;
-// result = pAsset->openChunk(NULL, fd, offset, length);
-// if (result != NO_ERROR)
-// return NULL;
-// pAsset->mAccessMode = mode;
-// return pAsset;
-//}
-///*
-// * Create a new Asset from compressed data in an open file.
-// */
-///*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
-// int compressionMethod, size_t uncompressedLen, size_t compressedLen,
-// AccessMode mode)
-//{
-// _CompressedAsset* pAsset;
-// status_t result;
-// pAsset = new _CompressedAsset;
-// result = pAsset->openChunk(fd, offset, compressionMethod,
-// uncompressedLen, compressedLen);
-// if (result != NO_ERROR)
-// return NULL;
-// pAsset->mAccessMode = mode;
-// return pAsset;
-//}
-//#endif
-///*
-// * Create a new Asset from a memory mapping.
-// */
-///*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
-// AccessMode mode)
-//{
-// _FileAsset* pAsset;
-// status_t result;
-// pAsset = new _FileAsset;
-// result = pAsset->openChunk(dataMap);
-// if (result != NO_ERROR)
-// return NULL;
-// pAsset->mAccessMode = mode;
-// return pAsset;
-//}
-///*
-// * Create a new Asset from compressed data in a memory mapping.
-// */
-///*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
-// size_t uncompressedLen, AccessMode mode)
-//{
-// _CompressedAsset* pAsset;
-// status_t result;
-// pAsset = new _CompressedAsset;
-// result = pAsset->openChunk(dataMap, uncompressedLen);
-// if (result != NO_ERROR)
-// return NULL;
-// pAsset->mAccessMode = mode;
-// return pAsset;
-//}
-///*
-// * Do generic seek() housekeeping. Pass in the offset/whence values from
-// * the seek request, along with the current chunk offset and the chunk
-// * length.
-// *
-// * Returns the new chunk offset, or -1 if the seek is illegal.
-// */
-//long handleSeek(long offset, int whence, long curPosn, long maxPosn)
-//{
-// long newOffset;
-// switch (whence) {
-// case SEEK_SET:
-// newOffset = offset;
-// break;
-// case SEEK_CUR:
-// newOffset = curPosn + offset;
-// break;
-// case SEEK_END:
-// newOffset = maxPosn + offset;
-// break;
-// default:
-// ALOGW("unexpected whence %d\n", whence);
-// // this was happening due to an off64_t size mismatch
-// assert(false);
-// return (off64_t) -1;
-// }
-// if (newOffset < 0 || newOffset > maxPosn) {
-// ALOGW("seek out of range: want %ld, end=%ld\n",
-// (long) newOffset, (long) maxPosn);
-// return (off64_t) -1;
-// }
-// return newOffset;
-//}
-///*
-// * ===========================================================================
-// * _FileAsset
-// * ===========================================================================
-// */
-///*
-// * Constructor.
-// */
-//_FileAsset::_FileAsset(void)
-// : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
-//{
-// // Register the Asset with the global list here after it is fully constructed and its
-// // vtable pointer points to this concrete type. b/31113965
-// registerAsset(this);
-//}
-///*
-// * Destructor. Release resources.
-// */
-//_FileAsset::~_FileAsset(void)
-//{
-// close();
-// // Unregister the Asset from the global list here before it is destructed and while its vtable
-// // pointer still points to this concrete type. b/31113965
-// unregisterAsset(this);
-//}
-///*
-// * Operate on a chunk of an uncompressed file.
-// *
-// * Zero-length chunks are allowed.
-// */
-//status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
-//{
-// assert(mFp == NULL); // no reopen
-// assert(mMap == NULL);
-// assert(fd >= 0);
-// assert(offset >= 0);
-// /*
-// * Seek to end to get file length.
-// */
-// off64_t fileLength;
-// fileLength = lseek64(fd, 0, SEEK_END);
-// if (fileLength == (off64_t) -1) {
-// // probably a bad file descriptor
-// ALOGD("failed lseek (errno=%d)\n", errno);
-// return UNKNOWN_ERROR;
-// }
-// if ((off64_t) (offset + length) > fileLength) {
-// ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
-// (long) offset, (long) length, (long) fileLength);
-// return BAD_INDEX;
-// }
-// /* after fdopen, the fd will be closed on fclose() */
-// mFp = fdopen(fd, "rb");
-// if (mFp == NULL)
-// return UNKNOWN_ERROR;
-// mStart = offset;
-// mLength = length;
-// assert(mOffset == 0);
-// /* seek the FILE* to the start of chunk */
-// if (fseek(mFp, mStart, SEEK_SET) != 0) {
-// assert(false);
-// }
-// mFileName = fileName != NULL ? strdup(fileName) : NULL;
-// return NO_ERROR;
-//}
-///*
-// * Create the chunk from the map.
-// */
-//status_t _FileAsset::openChunk(FileMap* dataMap)
-//{
-// assert(mFp == NULL); // no reopen
-// assert(mMap == NULL);
-// assert(dataMap != NULL);
-// mMap = dataMap;
-// mStart = -1; // not used
-// mLength = dataMap->getDataLength();
-// assert(mOffset == 0);
-// return NO_ERROR;
-//}
-///*
-// * Read a chunk of data.
-// */
-//ssize_t _FileAsset::read(void* buf, size_t count)
-//{
-// size_t maxLen;
-// size_t actual;
-// assert(mOffset >= 0 && mOffset <= mLength);
-// if (getAccessMode() == ACCESS_BUFFER) {
-// /*
-// * On first access, read or map the entire file. The caller has
-// * requested buffer access, either because they're going to be
-// * using the buffer or because what they're doing has appropriate
-// * performance needs and access patterns.
-// */
-// if (mBuf == NULL)
-// getBuffer(false);
-// }
-// /* adjust count if we're near EOF */
-// maxLen = mLength - mOffset;
-// if (count > maxLen)
-// count = maxLen;
-// if (!count)
-// return 0;
-// if (mMap != NULL) {
-// /* copy from mapped area */
-// //printf("map read\n");
-// memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
-// actual = count;
-// } else if (mBuf != NULL) {
-// /* copy from buffer */
-// //printf("buf read\n");
-// memcpy(buf, (char*)mBuf + mOffset, count);
-// actual = count;
-// } else {
-// /* read from the file */
-// //printf("file read\n");
-// if (ftell(mFp) != mStart + mOffset) {
-// ALOGE("Hosed: %ld != %ld+%ld\n",
-// ftell(mFp), (long) mStart, (long) mOffset);
-// assert(false);
-// }
-// /*
-// * This returns 0 on error or eof. We need to use ferror() or feof()
-// * to tell the difference, but we don't currently have those on the
-// * device. However, we know how much data is *supposed* to be in the
-// * file, so if we don't read the full amount we know something is
-// * hosed.
-// */
-// actual = fread(buf, 1, count, mFp);
-// if (actual == 0) // something failed -- I/O error?
-// return -1;
-// assert(actual == count);
-// }
-// mOffset += actual;
-// return actual;
-//}
-///*
-// * Seek to a new position.
-// */
-//off64_t _FileAsset::seek(off64_t offset, int whence)
-//{
-// off64_t newPosn;
-// off64_t actualOffset;
-// // compute new position within chunk
-// newPosn = handleSeek(offset, whence, mOffset, mLength);
-// if (newPosn == (off64_t) -1)
-// return newPosn;
-// actualOffset = mStart + newPosn;
-// if (mFp != NULL) {
-// if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
-// return (off64_t) -1;
-// }
-// mOffset = actualOffset - mStart;
-// return mOffset;
-//}
-///*
-// * Close the asset.
-// */
-//void _FileAsset::close(void)
-//{
-// if (mMap != NULL) {
-// delete mMap;
-// mMap = NULL;
-// }
-// if (mBuf != NULL) {
-// delete[] mBuf;
-// mBuf = NULL;
-// }
-// if (mFileName != NULL) {
-// free(mFileName);
-// mFileName = NULL;
-// }
-// if (mFp != NULL) {
-// // can only be NULL when called from destructor
-// // (otherwise we would never return this object)
-// fclose(mFp);
-// mFp = NULL;
-// }
-//}
-///*
-// * Return a read-only pointer to a buffer.
-// *
-// * We can either read the whole thing in or map the relevant piece of
-// * the source file. Ideally a map would be established at a higher
-// * level and we'd be using a different object, but we didn't, so we
-// * deal with it here.
-// */
-//const void* _FileAsset::getBuffer(bool wordAligned)
-//{
-// /* subsequent requests just use what we did previously */
-// if (mBuf != NULL)
-// return mBuf;
-// if (mMap != NULL) {
-// if (!wordAligned) {
-// return mMap->getDataPtr();
-// }
-// return ensureAlignment(mMap);
-// }
-// assert(mFp != NULL);
-// if (mLength < kReadVsMapThreshold) {
-// unsigned char* buf;
-// long allocLen;
-// /* zero-length files are allowed; not sure about zero-len allocs */
-// /* (works fine with gcc + x86linux) */
-// allocLen = mLength;
-// if (mLength == 0)
-// allocLen = 1;
-// buf = new unsigned char[allocLen];
-// if (buf == NULL) {
-// ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
-// return NULL;
-// }
-// ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
-// if (mLength > 0) {
-// long oldPosn = ftell(mFp);
-// fseek(mFp, mStart, SEEK_SET);
-// if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
-// ALOGE("failed reading %ld bytes\n", (long) mLength);
-// delete[] buf;
-// return NULL;
-// }
-// fseek(mFp, oldPosn, SEEK_SET);
-// }
-// ALOGV(" getBuffer: loaded into buffer\n");
-// mBuf = buf;
-// return mBuf;
-// } else {
-// FileMap* map;
-// map = new FileMap;
-// if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
-// delete map;
-// return NULL;
-// }
-// ALOGV(" getBuffer: mapped\n");
-// mMap = map;
-// if (!wordAligned) {
-// return mMap->getDataPtr();
-// }
-// return ensureAlignment(mMap);
-// }
-//}
-//int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
-//{
-// if (mMap != NULL) {
-// const char* fname = mMap->getFileName();
-// if (fname == NULL) {
-// fname = mFileName;
-// }
-// if (fname == NULL) {
-// return -1;
-// }
-// *outStart = mMap->getDataOffset();
-// *outLength = mMap->getDataLength();
-// return open(fname, O_RDONLY | O_BINARY);
-// }
-// if (mFileName == NULL) {
-// return -1;
-// }
-// *outStart = mStart;
-// *outLength = mLength;
-// return open(mFileName, O_RDONLY | O_BINARY);
-//}
-//const void* _FileAsset::ensureAlignment(FileMap* map)
-//{
-// void* data = map->getDataPtr();
-// if ((((size_t)data)&0x3) == 0) {
-// // We can return this directly if it is aligned on a word
-// // boundary.
-// ALOGV("Returning aligned FileAsset %p (%s).", this,
-// getAssetSource());
-// return data;
-// }
-// // If not aligned on a word boundary, then we need to copy it into
-// // our own buffer.
-// ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this,
-// getAssetSource(), (int)mLength);
-// unsigned char* buf = new unsigned char[mLength];
-// if (buf == NULL) {
-// ALOGE("alloc of %ld bytes failed\n", (long) mLength);
-// return NULL;
-// }
-// memcpy(buf, data, mLength);
-// mBuf = buf;
-// return buf;
-//}
-/*
- * ===========================================================================
- * _CompressedAsset
- * ===========================================================================
- */
-private static class _CompressedAsset extends Asset {
- private long mStart; // offset to start of compressed data
- private long mCompressedLen; // length of the compressed data
- private long mUncompressedLen; // length of the uncompressed data
- private long mOffset; // current offset, 0 == start of uncomp data
- //FileMap* mMap; // for memory-mapped input
- private int mFd; // for file input
- //StreamingZipInflater mZipInflater; // for streaming large compressed assets
- private byte[] mBuf; // for getBuffer()
-
-/*
- * Constructor.
- */
-_CompressedAsset(RandomAccessFile f) {
- super(f);
- mStart = 0;
- mCompressedLen = 0;
- mUncompressedLen = 0;
- mOffset = 0;
- // mMap = null;
- mFd = -1;
- //mZipInflater = null;
- mBuf = null;
-
- // Register the Asset with the global list here after it is fully constructed and its
- // vtable pointer points to this concrete type. b/31113965
- registerAsset(this);
-
-
-}
-/*
- * Destructor. Release resources.
- */
-@Override
-public void finalize()
+ static Asset createFromCompressedFile(String fileName,
+ AccessMode mode)
{
- // close();
- // Unregister the Asset from the global list here before it is destructed and while its vtable
- // pointer still points to this concrete type. b/31113965
- unregisterAsset(this);
+ return null;
+ // _CompressedAsset pAsset;
+ // status_t result;
+ // off64_t fileLen;
+ // bool scanResult;
+ // long offset;
+ // int method;
+ // long uncompressedLen, compressedLen;
+ // int fd;
+ // fd = open(fileName, O_RDONLY | O_BINARY);
+ // if (fd < 0)
+ // return NULL;
+ // fileLen = lseek(fd, 0, SEEK_END);
+ // if (fileLen < 0) {
+ // ::close(fd);
+ // return NULL;
+ // }
+ // (void) lseek(fd, 0, SEEK_SET);
+ // /* want buffered I/O for the file scan; must dup so fclose() is safe */
+ // FILE* fp = fdopen(dup(fd), "rb");
+ // if (fp == NULL) {
+ // ::close(fd);
+ // return NULL;
+ // }
+ // unsigned long crc32;
+ // scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
+ // &compressedLen, &crc32);
+ // offset = ftell(fp);
+ // fclose(fp);
+ // if (!scanResult) {
+ // ALOGD("File '%s' is not in gzip format\n", fileName);
+ // ::close(fd);
+ // return NULL;
+ // }
+ // pAsset = new _CompressedAsset;
+ // result = pAsset->openChunk(fd, offset, method, uncompressedLen,
+ // compressedLen);
+ // if (result != NO_ERROR) {
+ // delete pAsset;
+ // return NULL;
+ // }
+ // pAsset->mAccessMode = mode;
+ // return pAsset;
+ }
+
+ public static Asset createFromZipEntry(ZipFileRO pZipFile, ZipEntryRO entry, String8 string8) {
+ return new CompressedAsset(pZipFile, entry, string8);
+ }
+
+ public abstract int getLength();
+
+ public abstract long size();
+
+ public abstract int read();
+
+ public abstract int read(byte[] b, int off, int len);
+
+ public abstract long seek(long offset, int whence);
+
+ public abstract void close();
+
+ /* set the asset source string */
+ void setAssetSource(String8 path) {
+ mAssetSource = path;
+ }
+
+ protected void registerAsset(Asset asset)
+{
+ int gCount = 0;
+ synchronized (gAssetLock) {
+ gAssets.add(asset);
+ gCount = gAssets.size();
+ }
+ if (kIsDebug) {
+ ALOGI("Creating Asset %p #%d\n", asset, gCount);
+ }
+
}
-///*
-// * Open a chunk of compressed data inside a file.
-// *
-// * This currently just sets up some values and returns. On the first
-// * read, we expand the entire file into a buffer and return data from it.
-// */
-//status_t _CompressedAsset::openChunk(int fd, off64_t offset,
-// int compressionMethod, size_t uncompressedLen, size_t compressedLen)
-//{
-// assert(mFd < 0); // no re-open
-// assert(mMap == NULL);
-// assert(fd >= 0);
-// assert(offset >= 0);
-// assert(compressedLen > 0);
-// if (compressionMethod != ZipFileRO::kCompressDeflated) {
-// assert(false);
-// return UNKNOWN_ERROR;
-// }
-// mStart = offset;
-// mCompressedLen = compressedLen;
-// mUncompressedLen = uncompressedLen;
-// assert(mOffset == 0);
-// mFd = fd;
-// assert(mBuf == NULL);
-// if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
-// mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
-// }
-// return NO_ERROR;
-//}
-///*
-// * Open a chunk of compressed data in a mapped region.
-// *
-// * Nothing is expanded until the first read call.
-// */
-//status_t _CompressedAsset::openChunk(FileMap* dataMap, size_t uncompressedLen)
-//{
-// assert(mFd < 0); // no re-open
-// assert(mMap == NULL);
-// assert(dataMap != NULL);
-// mMap = dataMap;
-// mStart = -1; // not used
-// mCompressedLen = dataMap->getDataLength();
-// mUncompressedLen = uncompressedLen;
-// assert(mOffset == 0);
-// if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
-// mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
-// }
-// return NO_ERROR;
-//}
-///*
-// * Read data from a chunk of compressed data.
-// *
-// * [For now, that's just copying data out of a buffer.]
-// */
-//ssize_t _CompressedAsset::read(void* buf, size_t count)
-//{
-// size_t maxLen;
-// size_t actual;
-// assert(mOffset >= 0 && mOffset <= mUncompressedLen);
-// /* If we're relying on a streaming inflater, go through that */
-// if (mZipInflater) {
-// actual = mZipInflater->read(buf, count);
-// } else {
-// if (mBuf == NULL) {
-// if (getBuffer(false) == NULL)
-// return -1;
-// }
-// assert(mBuf != NULL);
-// /* adjust count if we're near EOF */
-// maxLen = mUncompressedLen - mOffset;
-// if (count > maxLen)
-// count = maxLen;
-// if (!count)
-// return 0;
-// /* copy from buffer */
-// //printf("comp buf read\n");
-// memcpy(buf, (char*)mBuf + mOffset, count);
-// actual = count;
-// }
-// mOffset += actual;
-// return actual;
-//}
-///*
-// * Handle a seek request.
-// *
-// * If we're working in a streaming mode, this is going to be fairly
-// * expensive, because it requires plowing through a bunch of compressed
-// * data.
-// */
-//off64_t _CompressedAsset::seek(off64_t offset, int whence)
-//{
-// off64_t newPosn;
-// // compute new position within chunk
-// newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
-// if (newPosn == (off64_t) -1)
-// return newPosn;
-// if (mZipInflater) {
-// mZipInflater->seekAbsolute(newPosn);
-// }
-// mOffset = newPosn;
-// return mOffset;
-//}
-///*
-// * Close the asset.
-// */
-//void _CompressedAsset::close(void)
-//{
-// if (mMap != NULL) {
-// delete mMap;
-// mMap = NULL;
-// }
-// delete[] mBuf;
-// mBuf = NULL;
-// delete mZipInflater;
-// mZipInflater = NULL;
-// if (mFd > 0) {
-// ::close(mFd);
-// mFd = -1;
-// }
-//}
-///*
-// * Get a pointer to a read-only buffer of data.
-// *
-// * The first time this is called, we expand the compressed data into a
-// * buffer.
-// */
-//const void* _CompressedAsset::getBuffer(bool)
-//{
-// unsigned char* buf = NULL;
-// if (mBuf != NULL)
-// return mBuf;
-// /*
-// * Allocate a buffer and read the file into it.
-// */
-// buf = new unsigned char[mUncompressedLen];
-// if (buf == NULL) {
-// ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
-// goto bail;
-// }
-// if (mMap != NULL) {
-// if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf,
-// mUncompressedLen, mCompressedLen))
-// goto bail;
-// } else {
-// assert(mFd >= 0);
-// /*
-// * Seek to the start of the compressed data.
-// */
-// if (lseek(mFd, mStart, SEEK_SET) != mStart)
-// goto bail;
-// /*
-// * Expand the data into it.
-// */
-// if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
-// mCompressedLen))
-// goto bail;
-// }
-// /*
-// * Success - now that we have the full asset in RAM we
-// * no longer need the streaming inflater
-// */
-// delete mZipInflater;
-// mZipInflater = NULL;
-// mBuf = buf;
-// buf = NULL;
-//bail:
-// delete[] buf;
-// return mBuf;
+
+ protected void unregisterAsset(Asset asset) {
+ int gCount = 0;
+ synchronized (gAssetLock) {
+ gAssets.remove(asset);
+ gCount = gAssets.size();
+ }
+ if (kIsDebug) {
+ ALOGI("Destroying Asset in %p #%d\n", asset, gCount);
+ }
+}
+
+ protected int getGlobalCount()
+{
+ synchronized (gAssetLock) {
+ return gAssets.size();
+ }
+}
+
+ /*
+ * Return a read-only pointer to a buffer.
+ *
+ * We can either read the whole thing in or map the relevant piece of
+ * the source file. Ideally a map would be established at a higher
+ * level and we'd be using a different object, but we didn't, so we
+ * deal with it here.
+ */
+ public byte[] getBuffer(boolean wordAligned) {
+ int size = (int) size();
+ byte[] bytes = new byte[size];
+ if (read(bytes, 0, size) != size) {
+ throw new RuntimeException("failed to read " + size);
+ }
+ return bytes;
+ }
+
+ // public:
+// virtual ~Asset(void) = default;
+// static int32_t getGlobalCount();
+// static String8 getAssetAllocations();
//
-}
+// /* used when opening an asset */
+ public enum AccessMode {
+ ACCESS_UNKNOWN,
+ /* read chunks, and seek forward and backward */
+ ACCESS_RANDOM,
+ /* read sequentially, with an occasional forward seek */
+ ACCESS_STREAMING,
+ /* caller plans to ask for a read-only buffer with all data */
+ ACCESS_BUFFER,
+ }
+
+ //const void* _FileAsset::getBuffer(bool wordAligned)
+ //{
+ // /* subsequent requests just use what we did previously */
+ // if (mBuf != NULL)
+ // return mBuf;
+ // if (mMap != NULL) {
+ // if (!wordAligned) {
+ // return mMap->getDataPtr();
+ // }
+ // return ensureAlignment(mMap);
+ // }
+ // assert(mFp != NULL);
+ // if (mLength < kReadVsMapThreshold) {
+ // unsigned char* buf;
+ // long allocLen;
+ // /* zero-length files are allowed; not sure about zero-len allocs */
+ // /* (works fine with gcc + x86linux) */
+ // allocLen = mLength;
+ // if (mLength == 0)
+ // allocLen = 1;
+ // buf = new unsigned char[allocLen];
+ // if (buf == NULL) {
+ // ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
+ // return NULL;
+ // }
+ // ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
+ // if (mLength > 0) {
+ // long oldPosn = ftell(mFp);
+ // fseek(mFp, mStart, SEEK_SET);
+ // if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
+ // ALOGE("failed reading %ld bytes\n", (long) mLength);
+ // delete[] buf;
+ // return NULL;
+ // }
+ // fseek(mFp, oldPosn, SEEK_SET);
+ // }
+ // ALOGV(" getBuffer: loaded into buffer\n");
+ // mBuf = buf;
+ // return mBuf;
+ // } else {
+ // FileMap* map;
+ // map = new FileMap;
+ // if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
+ // delete map;
+ // return NULL;
+ // }
+ // ALOGV(" getBuffer: mapped\n");
+ // mMap = map;
+ // if (!wordAligned) {
+ // return mMap->getDataPtr();
+ // }
+ // return ensureAlignment(mMap);
+ // }
+ //}
+ //int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
+ //{
+ // if (mMap != NULL) {
+ // const char* fname = mMap->getFileName();
+ // if (fname == NULL) {
+ // fname = mFileName;
+ // }
+ // if (fname == NULL) {
+ // return -1;
+ // }
+ // *outStart = mMap->getDataOffset();
+ // *outLength = mMap->getDataLength();
+ // return open(fname, O_RDONLY | O_BINARY);
+ // }
+ // if (mFileName == NULL) {
+ // return -1;
+ // }
+ // *outStart = mStart;
+ // *outLength = mLength;
+ // return open(mFileName, O_RDONLY | O_BINARY);
+ //}
+ //const void* _FileAsset::ensureAlignment(FileMap* map)
+ //{
+ // void* data = map->getDataPtr();
+ // if ((((size_t)data)&0x3) == 0) {
+ // // We can return this directly if it is aligned on a word
+ // // boundary.
+ // ALOGV("Returning aligned FileAsset %p (%s).", this,
+ // getAssetSource());
+ // return data;
+ // }
+ // // If not aligned on a word boundary, then we need to copy it into
+ // // our own buffer.
+ // ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this,
+ // getAssetSource(), (int)mLength);
+ // unsigned char* buf = new unsigned char[mLength];
+ // if (buf == NULL) {
+ // ALOGE("alloc of %ld bytes failed\n", (long) mLength);
+ // return NULL;
+ // }
+ // memcpy(buf, data, mLength);
+ // mBuf = buf;
+ // return buf;
+ //}
+ /*
+ * ===========================================================================
+ * _CompressedAsset
+ * ===========================================================================
+ */
+ private static class CompressedAsset extends Asset {
+
+ private final ZipFileRO pZipFile;
+ private final ZipEntryRO entry;
+ private final InputStream entryInputStream;
+ private long mStart; // offset to start of compressed data
+ private long mCompressedLen; // length of the compressed data
+ private long mUncompressedLen; // length of the uncompressed data
+ private long mOffset; // current offset, 0 == start of uncomp data
+ //FileMap* mMap; // for memory-mapped input
+ private int mFd; // for file input
+ //StreamingZipInflater mZipInflater; // for streaming large compressed assets
+ private byte[] mBuf; // for getBuffer()
+
+ /*
+ * Constructor.
+ */
+ public CompressedAsset(ZipFileRO pZipFile, ZipEntryRO entry, String8 string8) {
+ this.pZipFile = pZipFile;
+ this.entry = entry;
+ mStart = 0;
+ mCompressedLen = 0;
+ mUncompressedLen = 0;
+ mOffset = 0;
+ // mMap = null;
+ mFd = -1;
+ //mZipInflater = null;
+ mBuf = null;
+
+ // Register the Asset with the global list here after it is fully constructed and its
+ // vtable pointer points to this concrete type. b/31113965
+ registerAsset(this);
+
+ ZipEntry zipEntry = entry.entry;
+ ZipFile zipFile = pZipFile.mHandle.zipFile;
+ try {
+ entryInputStream = zipFile.getInputStream(zipEntry);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int getLength() {
+ return (int) entry.entry.getSize();
+ }
+
+ @Override
+ public long size() {
+ return entry.entry.getSize();
+ }
+
+ @Override
+ public int read() {
+ try {
+ return entryInputStream.read();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) {
+ try {
+ return entryInputStream.read(b, off, len);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public long seek(long offset, int whence) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ public void close() {
+ try {
+ entryInputStream.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /*
+ * Destructor. Release resources.
+ */
+ @Override
+ public void finalize()
+ {
+ // close();
+ // Unregister the Asset from the global list here before it is destructed and while its vtable
+ // pointer still points to this concrete type. b/31113965
+ unregisterAsset(this);
+ }
+ ///*
+ // * Open a chunk of compressed data inside a file.
+ // *
+ // * This currently just sets up some values and returns. On the first
+ // * read, we expand the entire file into a buffer and return data from it.
+ // */
+ //status_t _CompressedAsset::openChunk(int fd, off64_t offset,
+ // int compressionMethod, size_t uncompressedLen, size_t compressedLen)
+ //{
+ // assert(mFd < 0); // no re-open
+ // assert(mMap == NULL);
+ // assert(fd >= 0);
+ // assert(offset >= 0);
+ // assert(compressedLen > 0);
+ // if (compressionMethod != ZipFileRO::kCompressDeflated) {
+ // assert(false);
+ // return UNKNOWN_ERROR;
+ // }
+ // mStart = offset;
+ // mCompressedLen = compressedLen;
+ // mUncompressedLen = uncompressedLen;
+ // assert(mOffset == 0);
+ // mFd = fd;
+ // assert(mBuf == NULL);
+ // if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
+ // mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
+ // }
+ // return NO_ERROR;
+ //}
+ ///*
+ // * Open a chunk of compressed data in a mapped region.
+ // *
+ // * Nothing is expanded until the first read call.
+ // */
+ //status_t _CompressedAsset::openChunk(FileMap* dataMap, size_t uncompressedLen)
+ //{
+ // assert(mFd < 0); // no re-open
+ // assert(mMap == NULL);
+ // assert(dataMap != NULL);
+ // mMap = dataMap;
+ // mStart = -1; // not used
+ // mCompressedLen = dataMap->getDataLength();
+ // mUncompressedLen = uncompressedLen;
+ // assert(mOffset == 0);
+ // if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
+ // mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
+ // }
+ // return NO_ERROR;
+ //}
+ ///*
+ // * Read data from a chunk of compressed data.
+ // *
+ // * [For now, that's just copying data out of a buffer.]
+ // */
+ //ssize_t _CompressedAsset::read(void* buf, size_t count)
+ //{
+ // size_t maxLen;
+ // size_t actual;
+ // assert(mOffset >= 0 && mOffset <= mUncompressedLen);
+ // /* If we're relying on a streaming inflater, go through that */
+ // if (mZipInflater) {
+ // actual = mZipInflater->read(buf, count);
+ // } else {
+ // if (mBuf == NULL) {
+ // if (getBuffer(false) == NULL)
+ // return -1;
+ // }
+ // assert(mBuf != NULL);
+ // /* adjust count if we're near EOF */
+ // maxLen = mUncompressedLen - mOffset;
+ // if (count > maxLen)
+ // count = maxLen;
+ // if (!count)
+ // return 0;
+ // /* copy from buffer */
+ // //printf("comp buf read\n");
+ // memcpy(buf, (char*)mBuf + mOffset, count);
+ // actual = count;
+ // }
+ // mOffset += actual;
+ // return actual;
+ //}
+ ///*
+ // * Handle a seek request.
+ // *
+ // * If we're working in a streaming mode, this is going to be fairly
+ // * expensive, because it requires plowing through a bunch of compressed
+ // * data.
+ // */
+ //off64_t _CompressedAsset::seek(off64_t offset, int whence)
+ //{
+ // off64_t newPosn;
+ // // compute new position within chunk
+ // newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
+ // if (newPosn == (off64_t) -1)
+ // return newPosn;
+ // if (mZipInflater) {
+ // mZipInflater->seekAbsolute(newPosn);
+ // }
+ // mOffset = newPosn;
+ // return mOffset;
+ //}
+ ///*
+ // * Close the asset.
+ // */
+ //void _CompressedAsset::close(void)
+ //{
+ // if (mMap != NULL) {
+ // delete mMap;
+ // mMap = NULL;
+ // }
+ // delete[] mBuf;
+ // mBuf = NULL;
+ // delete mZipInflater;
+ // mZipInflater = NULL;
+ // if (mFd > 0) {
+ // ::close(mFd);
+ // mFd = -1;
+ // }
+ //}
+ ///*
+ // * Get a pointer to a read-only buffer of data.
+ // *
+ // * The first time this is called, we expand the compressed data into a
+ // * buffer.
+ // */
+ //const void* _CompressedAsset::getBuffer(bool)
+ //{
+ // unsigned char* buf = NULL;
+ // if (mBuf != NULL)
+ // return mBuf;
+ // /*
+ // * Allocate a buffer and read the file into it.
+ // */
+ // buf = new unsigned char[mUncompressedLen];
+ // if (buf == NULL) {
+ // ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
+ // goto bail;
+ // }
+ // if (mMap != NULL) {
+ // if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf,
+ // mUncompressedLen, mCompressedLen))
+ // goto bail;
+ // } else {
+ // assert(mFd >= 0);
+ // /*
+ // * Seek to the start of the compressed data.
+ // */
+ // if (lseek(mFd, mStart, SEEK_SET) != mStart)
+ // goto bail;
+ // /*
+ // * Expand the data into it.
+ // */
+ // if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
+ // mCompressedLen))
+ // goto bail;
+ // }
+ // /*
+ // * Success - now that we have the full asset in RAM we
+ // * no longer need the streaming inflater
+ // */
+ // delete mZipInflater;
+ // mZipInflater = NULL;
+ // mBuf = buf;
+ // buf = NULL;
+ //bail:
+ // delete[] buf;
+ // return mBuf;
+ //
+ }
}
diff --git a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java
index c35a66d..609497e 100644
--- a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java
+++ b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java
@@ -1,5 +1,7 @@
package org.robolectric.res.android;
+import static org.robolectric.res.android.Util.ALOGD;
+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.ATRACE_CALL;
@@ -9,20 +11,24 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.ref.WeakReference;
import java.net.URL;
-import java.nio.file.Path;
+import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.annotation.Nullable;
import org.robolectric.res.android.Asset.AccessMode;
-import org.robolectric.res.android.CppAssetManager.FileType;
+import org.robolectric.res.android.ZipFileRO.ZipEntryRO;
// transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/libs/androidfw/AssetManager.cpp
public class CppAssetManager {
+ private static final boolean kIsDebug = false;
enum FileType {
kFileTypeUnknown,
@@ -67,8 +73,7 @@
private final Object mLock = new Object();
- //ZipSet mZipSet;
- Object mZipSet;
+ ZipSet mZipSet = new ZipSet();
private final List<asset_path> mAssetPaths = new ArrayList<>();
private String mLocale;
@@ -88,7 +93,7 @@
//
// static Asset final kExcludedAsset = (Asset*) 0xd000000d;
- static final Asset kExcludedAsset = new Asset(null);
+ static final Asset kExcludedAsset = new FileAsset(null);
//
// static volatile int gCount = 0;
//
@@ -537,7 +542,7 @@
}
boolean appendPathToResTable(final asset_path ap, boolean appAsLib) {
- URL resource = getClass().getResource("/resources.ap_");
+ URL resource = getClass().getResource("/resources.ap_"); // todo get this from asset_path
System.out.println("Reading ARSC file from " + resource);
LOG_FATAL_IF(resource == null, "Could not find resources.ap_");
try {
@@ -820,48 +825,46 @@
/* look inside the zip file */
} else {
- throw new UnsupportedOperationException("not implemented yet");
-// String8 path = new String8(fileName);
-//
-// /* check the appropriate Zip file */
-// ZipFileRO* pZip = getZipFileLocked(ap);
-// if (pZip != null) {
-// //printf("GOT zip, checking NA '%s'\n", (final char*) path);
-// ZipEntryRO entry = pZip.findEntryByName(path.string());
-// if (entry != null) {
-// //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
-// pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
-// pZip.releaseEntry(entry);
-// }
-// }
-//
-// if (pAsset != null) {
-// /* create a "source" name, for debug/display */
-// pAsset.setAssetSource(
-// createZipSourceNameLocked(ZipSet.getPathName(ap.path.string()), String8(""),
-// String8(fileName)));
-// }
+ String8 path = new String8(fileName);
+
+ /* check the appropriate Zip file */
+ ZipFileRO pZip = getZipFileLocked(ap);
+ if (pZip != null) {
+ //printf("GOT zip, checking NA '%s'\n", (final char*) path);
+ ZipEntryRO entry = pZip.findEntryByName(path.string());
+ if (entry != null) {
+ //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
+ pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
+ pZip.releaseEntry(entry);
+ }
+ }
+
+ if (pAsset != null) {
+ /* create a "source" name, for debug/display */
+ pAsset.setAssetSource(
+ createZipSourceNameLocked(ap.path.string(), "", fileName));
+ }
}
- return pAsset;
+ return pAsset;
}
-//
-// /*
-// * Create a "source name" for a file from a Zip archive.
-// */
-// String8 createZipSourceNameLocked(final String8& zipFileName,
-// final String8& dirName, final String8& fileName)
-// {
-// String8 sourceName("zip:");
-// sourceName.append(zipFileName);
-// sourceName.append(":");
-// if (dirName.length() > 0) {
-// sourceName.appendPath(dirName);
-// }
-// sourceName.appendPath(fileName);
-// return sourceName;
-// }
-//
+
+ /*
+ * Create a "source name" for a file from a Zip archive.
+ */
+ String8 createZipSourceNameLocked(final String zipFileName,
+ final String dirName, final String fileName)
+ {
+ String8 sourceName = new String8("zip:");
+ sourceName.append(zipFileName);
+ sourceName.append(":");
+ if (dirName.length() > 0) {
+ sourceName.appendPath(dirName);
+ }
+ sourceName.appendPath(fileName);
+ return sourceName;
+ }
+
// /*
// * Create a path to a loose asset (asset-base/app/rootDir).
// */
@@ -872,17 +875,17 @@
// return path;
// }
//
-// /*
-// * Return a pointer to one of our open Zip archives. Returns null if no
-// * matching Zip file exists.
-// */
-// ZipFileRO* getZipFileLocked(final asset_path& ap)
-// {
-// ALOGV("getZipFileLocked() in %p\n", this);
-//
-// return mZipSet.getZip(ap.path);
-// }
-//
+ /*
+ * Return a pointer to one of our open Zip archives. Returns null if no
+ * matching Zip file exists.
+ */
+ ZipFileRO getZipFileLocked(final asset_path ap)
+ {
+ ALOGV("getZipFileLocked() in %s\n", this);
+
+ return mZipSet.getZip(ap.path.string());
+ }
+
/*
* Try to open an asset from a file on disk.
*
@@ -910,37 +913,38 @@
return pAsset;
}
-//
-// /*
-// * Given an entry in a Zip archive, create a new Asset object.
-// *
-// * If the entry is uncompressed, we may want to create or share a
-// * slice of shared memory.
-// */
-// Asset* openAssetFromZipLocked(final ZipFileRO* pZipFile,
-// final ZipEntryRO entry, AccessMode mode, final String8& entryName)
-// {
-// Asset* pAsset = null;
-//
-// // TODO: look for previously-created shared memory slice?
-// uint16_t method;
-// uint32_t uncompressedLen;
-//
-// //printf("USING Zip '%s'\n", pEntry.getFileName());
-//
-// if (!pZipFile.getEntryInfo(entry, &method, &uncompressedLen, null, null,
-// null, null))
-// {
-// ALOGW("getEntryInfo failed\n");
-// return null;
-// }
-//
-// FileMap* dataMap = pZipFile.createEntryFileMap(entry);
+
+ /*
+ * Given an entry in a Zip archive, create a new Asset object.
+ *
+ * If the entry is uncompressed, we may want to create or share a
+ * slice of shared memory.
+ */
+ Asset openAssetFromZipLocked(final ZipFileRO pZipFile,
+ final ZipEntryRO entry, AccessMode mode, final String8 entryName)
+ {
+ Asset pAsset = null;
+
+ // TODO: look for previously-created shared memory slice?
+ Ref<Short> method = new Ref<>((short) 0);
+ Ref<Long> uncompressedLen = new Ref<>(0L);
+
+ //printf("USING Zip '%s'\n", pEntry.getFileName());
+
+ if (!pZipFile.getEntryInfo(entry, method, uncompressedLen, null, null,
+ null, null))
+ {
+ ALOGW("getEntryInfo failed\n");
+ return null;
+ }
+
+ return Asset.createFromZipEntry(pZipFile, entry, entryName);
+// FileMap dataMap = pZipFile.createEntryFileMap(entry);
// if (dataMap == null) {
// ALOGW("create map from entry failed\n");
// return null;
// }
-//
+//
// if (method == ZipFileRO.kCompressStored) {
// pAsset = Asset.createFromUncompressedMap(dataMap, mode);
// ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(),
@@ -955,10 +959,10 @@
// /* unexpected */
// ALOGW("create from segment failed\n");
// }
-//
+
// return pAsset;
-// }
-//
+ }
+
// /*
// * Open a directory in the asset namespace.
// *
@@ -1412,61 +1416,78 @@
// }
// #endif
// }
-//
-// /*
-// * ===========================================================================
-// * SharedZip
-// * ===========================================================================
-// */
-//
-//
-// Mutex SharedZip.gLock;
-// DefaultKeyedVector<String8, wp<SharedZip> > SharedZip.gOpen;
-//
-// SharedZip.SharedZip(final String8& path, time_t modWhen)
-// : mPath(path), mZipFile(null), mModWhen(modWhen),
-// mResourceTableAsset(null), mResourceTable(null)
-// {
-// if (kIsDebug) {
-// ALOGI("Creating SharedZip %p %s\n", this, (final char*)mPath);
-// }
-// ALOGV("+++ opening zip '%s'\n", mPath.string());
-// mZipFile = ZipFileRO.open(mPath.string());
-// if (mZipFile == null) {
-// ALOGD("failed to open Zip archive '%s'\n", mPath.string());
-// }
-// }
-//
-// sp<SharedZip> SharedZip.get(final String8& path,
-// boolean createIfNotPresent)
-// {
-// AutoMutex _l(gLock);
-// time_t modWhen = getFileModDate(path);
-// sp<SharedZip> zip = gOpen.valueFor(path).promote();
-// if (zip != null && zip.mModWhen == modWhen) {
-// return zip;
-// }
-// if (zip == null && !createIfNotPresent) {
-// return null;
-// }
-// zip = new SharedZip(path, modWhen);
-// gOpen.add(path, zip);
-// return zip;
-//
-// }
-//
-// ZipFileRO* SharedZip.getZip()
-// {
-// return mZipFile;
-// }
-//
+
+ /*
+ * ===========================================================================
+ * SharedZip
+ * ===========================================================================
+ */
+
+ static class SharedZip /*: public RefBase */{
+ String mPath;
+ ZipFileRO mZipFile;
+ long mModWhen;
+
+ Asset mResourceTableAsset;
+ ResTable mResourceTable;
+
+ List<asset_path> mOverlays;
+
+ final static Object gLock = new Object();
+ final static Map<String8, WeakReference<SharedZip>> gOpen = new HashMap<>();
+
+ public SharedZip(String path, long modWhen) {
+ this.mPath = path;
+ this.mZipFile = null;
+ this.mModWhen = modWhen;
+ this.mResourceTableAsset = null;
+ this.mResourceTable = null;
+
+ if (kIsDebug) {
+ ALOGI("Creating SharedZip %s %s\n", this, mPath);
+ }
+ ALOGV("+++ opening zip '%s'\n", mPath);
+ mZipFile = ZipFileRO.open(mPath);
+ if (mZipFile == null) {
+ ALOGD("failed to open Zip archive '%s'\n", mPath);
+ }
+ }
+
+ static SharedZip get(final String8 path) {
+ return get(path, true);
+ }
+
+ static SharedZip get(final String8 path, boolean createIfNotPresent) {
+ synchronized (gLock) {
+ long modWhen = getFileModDate(path.string());
+ WeakReference<SharedZip> ref = gOpen.get(path);
+ SharedZip zip = ref == null ? null : ref.get();
+ if (zip != null && zip.mModWhen == modWhen) {
+ return zip;
+ }
+ if (zip == null && !createIfNotPresent) {
+ return null;
+ }
+ zip = new SharedZip(path.string(), modWhen);
+ gOpen.put(path, new WeakReference<>(zip));
+ return zip;
+
+ }
+
+ }
+
+ ZipFileRO getZip()
+ {
+ return mZipFile;
+ }
+
// Asset* SharedZip.getResourceTableAsset()
// {
// AutoMutex _l(gLock);
// ALOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
// return mResourceTableAsset;
// }
-//
+//
// Asset* SharedZip.setResourceTableAsset(Asset* asset)
// {
// {
@@ -1482,13 +1503,13 @@
// delete asset;
// return mResourceTableAsset;
// }
-//
+//
// ResTable* SharedZip.getResourceTable()
// {
// ALOGV("Getting from SharedZip %p resource table %p\n", this, mResourceTable);
// return mResourceTable;
// }
-//
+//
// ResTable* SharedZip.setResourceTable(ResTable* res)
// {
// {
@@ -1501,18 +1522,18 @@
// delete res;
// return mResourceTable;
// }
-//
+//
// boolean SharedZip.isUpToDate()
// {
// time_t modWhen = getFileModDate(mPath.string());
// return mModWhen == modWhen;
// }
-//
+//
// void SharedZip.addOverlay(final asset_path& ap)
// {
// mOverlays.add(ap);
// }
-//
+//
// boolean SharedZip.getOverlay(int idx, asset_path* out) final
// {
// if (idx >= mOverlays.size()) {
@@ -1521,7 +1542,7 @@
// *out = mOverlays[idx];
// return true;
// }
-//
+//
// SharedZip.~SharedZip()
// {
// if (kIsDebug) {
@@ -1538,46 +1559,62 @@
// ALOGV("Closed '%s'\n", mPath.string());
// }
// }
-//
-// /*
-// * ===========================================================================
-// * ZipSet
-// * ===========================================================================
-// */
-//
-// /*
-// * Destructor. Close any open archives.
-// */
+//
+};
+
+
+ /*
+ * Manage a set of Zip files. For each file we need a pointer to the
+ * ZipFile and a time_t with the file's modification date.
+ *
+ * We currently only have two zip files (current app, "common" app).
+ * (This was originally written for 8, based on app/locale/vendor.)
+ */
+ static class ZipSet {
+
+ List<String> mZipPath = new ArrayList<>();
+ List<SharedZip> mZipFile = new ArrayList<>();
+
+ /*
+ * ===========================================================================
+ * ZipSet
+ * ===========================================================================
+ */
+
+ /*
+ * Destructor. Close any open archives.
+ */
// ZipSet.~ZipSet(void)
-// {
-// int N = mZipFile.size();
-// for (int i = 0; i < N; i++)
-// closeZip(i);
-// }
-//
-// /*
-// * Close a Zip file and reset the entry.
-// */
-// void ZipSet.closeZip(int idx)
-// {
-// mZipFile.editItemAt(idx) = null;
-// }
-//
-//
-// /*
-// * Retrieve the appropriate Zip file from the set.
-// */
-// ZipFileRO* ZipSet.getZip(final String8& path)
-// {
-// int idx = getIndex(path);
-// sp<SharedZip> zip = mZipFile[idx];
-// if (zip == null) {
-// zip = SharedZip.get(path);
-// mZipFile.editItemAt(idx) = zip;
-// }
-// return zip.getZip();
-// }
-//
+ protected void finalize()
+ {
+ int N = mZipFile.size();
+ for (int i = 0; i < N; i++)
+ closeZip(i);
+ }
+
+ /*
+ * Close a Zip file and reset the entry.
+ */
+ void closeZip(int idx)
+ {
+ mZipFile.set(idx, null);
+ }
+
+
+ /*
+ * Retrieve the appropriate Zip file from the set.
+ */
+ ZipFileRO getZip(final String path)
+ {
+ int idx = getIndex(path);
+ SharedZip zip = mZipFile.get(idx);
+ if (zip == null) {
+ zip = SharedZip.get(new String8(path));
+ mZipFile.set(idx, zip);
+ }
+ return zip.getZip();
+ }
+
// Asset* ZipSet.getZipResourceTableAsset(final String8& path)
// {
// int idx = getIndex(path);
@@ -1588,7 +1625,7 @@
// }
// return zip.getResourceTableAsset();
// }
-//
+//
// Asset* ZipSet.setZipResourceTableAsset(final String8& path,
// Asset* asset)
// {
@@ -1597,7 +1634,7 @@
// // doesn't make sense to call before previously accessing.
// return zip.setResourceTableAsset(asset);
// }
-//
+//
// ResTable* ZipSet.getZipResourceTable(final String8& path)
// {
// int idx = getIndex(path);
@@ -1608,7 +1645,7 @@
// }
// return zip.getResourceTable();
// }
-//
+//
// ResTable* ZipSet.setZipResourceTable(final String8& path,
// ResTable* res)
// {
@@ -1617,7 +1654,7 @@
// // doesn't make sense to call before previously accessing.
// return zip.setResourceTable(res);
// }
-//
+//
// /*
// * Generate the partial pathname for the specified archive. The caller
// * gets to prepend the asset root directory.
@@ -1628,7 +1665,7 @@
// {
// return String8(zipPath);
// }
-//
+//
// boolean ZipSet.isUpToDate()
// {
// final int N = mZipFile.size();
@@ -1639,14 +1676,14 @@
// }
// return true;
// }
-//
+//
// void ZipSet.addOverlay(final String8& path, final asset_path& overlay)
// {
// int idx = getIndex(path);
// sp<SharedZip> zip = mZipFile[idx];
// zip.addOverlay(overlay);
// }
-//
+//
// boolean ZipSet.getOverlay(final String8& path, int idx, asset_path* out) final
// {
// sp<SharedZip> zip = SharedZip.get(path, false);
@@ -1655,26 +1692,34 @@
// }
// return zip.getOverlay(idx, out);
// }
-//
-// /*
-// * Compute the zip file's index.
-// *
-// * "appName", "locale", and "vendor" should be set to null to indicate the
-// * default directory.
-// */
-// int ZipSet.getIndex(final String8& zip) final
-// {
-// final int N = mZipPath.size();
-// for (int i=0; i<N; i++) {
-// if (mZipPath[i] == zip) {
-// return i;
-// }
-// }
-//
-// mZipPath.add(zip);
-// mZipFile.add(null);
-//
-// return mZipPath.size()-1;
-// }
-
+//
+ /*
+ * Compute the zip file's index.
+ *
+ * "appName", "locale", and "vendor" should be set to null to indicate the
+ * default directory.
+ */
+ int getIndex(final String zip)
+ {
+ final int N = mZipPath.size();
+ for (int i=0; i<N; i++) {
+ if (Objects.equals(mZipPath.get(i), zip)) {
+ return i;
+ }
+ }
+
+ mZipPath.add(zip);
+ mZipFile.add(null);
+
+ return mZipPath.size()-1;
+ }
+ }
+
+ private static long getFileModDate(String path) {
+ try {
+ return Files.getLastModifiedTime(Paths.get(path)).toMillis();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/resources/src/main/java/org/robolectric/res/android/FileAsset.java b/resources/src/main/java/org/robolectric/res/android/FileAsset.java
new file mode 100644
index 0000000..1d8540e
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/FileAsset.java
@@ -0,0 +1,369 @@
+package org.robolectric.res.android;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Class providing access to a read-only asset. Asset objects are NOT thread-safe, and should not
+ * be shared across threads.
+ *
+ * transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/include/androidfw/Asset.h
+ * and https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/libs/androidfw/Asset.cpp
+ *
+ * Instances of this class provide read-only operations on a byte stream.
+ * Access may
+ * be optimized for streaming, random, or whole buffer modes. All operations are supported
+ * regardless of how the file was opened, but some things will be less efficient. [pass that
+ * in??]. "Asset" is the base class for all types of assets. The classes below
+ * provide most of the implementation. The AssetManager uses one of the static "create"
+ * functions defined here to create a new instance.
+ */
+public class FileAsset extends Asset {
+ protected final RandomAccessFile f;
+
+ public FileAsset(RandomAccessFile f) {
+ this.f = f;
+ }
+
+ @Override
+ public int getLength() {
+ return (int) size();
+ }
+
+ @Override
+ public long size() {
+ try {
+ return f.length();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int read() {
+ try {
+ return f.read();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) {
+ try {
+ return f.read(b, off, len);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public long seek(long offset, int whence) {
+ throw new UnsupportedOperationException("not yet implemented");
+// if (whence > 0) {
+// SEEK_END
+// } else if (whence < 0) {
+// SEEK_SET
+// } else {
+// SEEK_CUR
+// }
+// return f.seek();
+ }
+
+ @Override
+ public void close() {
+ if (onClose != null) {
+ onClose.run();
+ }
+ try {
+ f.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ //
+
+ /*
+ * Create a new Asset from a compressed file on disk. There is a fair chance
+ * that the file doesn't actually exist.
+ *
+ * We currently support gzip files. We might want to handle .bz2 someday.
+ */
+
+ //#if 0
+///*
+// * Create a new Asset from part of an open file.
+// */
+///*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
+// size_t length, AccessMode mode)
+//{
+// _FileAsset* pAsset;
+// status_t result;
+// pAsset = new _FileAsset;
+// result = pAsset->openChunk(NULL, fd, offset, length);
+// if (result != NO_ERROR)
+// return NULL;
+// pAsset->mAccessMode = mode;
+// return pAsset;
+//}
+///*
+// * Create a new Asset from compressed data in an open file.
+// */
+///*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
+// int compressionMethod, size_t uncompressedLen, size_t compressedLen,
+// AccessMode mode)
+//{
+// _CompressedAsset* pAsset;
+// status_t result;
+// pAsset = new _CompressedAsset;
+// result = pAsset->openChunk(fd, offset, compressionMethod,
+// uncompressedLen, compressedLen);
+// if (result != NO_ERROR)
+// return NULL;
+// pAsset->mAccessMode = mode;
+// return pAsset;
+//}
+//#endif
+///*
+// * Create a new Asset from a memory mapping.
+// */
+///*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
+// AccessMode mode)
+//{
+// _FileAsset* pAsset;
+// status_t result;
+// pAsset = new _FileAsset;
+// result = pAsset->openChunk(dataMap);
+// if (result != NO_ERROR)
+// return NULL;
+// pAsset->mAccessMode = mode;
+// return pAsset;
+//}
+///*
+// * Create a new Asset from compressed data in a memory mapping.
+// */
+///*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
+// size_t uncompressedLen, AccessMode mode)
+//{
+// _CompressedAsset* pAsset;
+// status_t result;
+// pAsset = new _CompressedAsset;
+// result = pAsset->openChunk(dataMap, uncompressedLen);
+// if (result != NO_ERROR)
+// return NULL;
+// pAsset->mAccessMode = mode;
+// return pAsset;
+//}
+///*
+// * Do generic seek() housekeeping. Pass in the offset/whence values from
+// * the seek request, along with the current chunk offset and the chunk
+// * length.
+// *
+// * Returns the new chunk offset, or -1 if the seek is illegal.
+// */
+//long handleSeek(long offset, int whence, long curPosn, long maxPosn)
+//{
+// long newOffset;
+// switch (whence) {
+// case SEEK_SET:
+// newOffset = offset;
+// break;
+// case SEEK_CUR:
+// newOffset = curPosn + offset;
+// break;
+// case SEEK_END:
+// newOffset = maxPosn + offset;
+// break;
+// default:
+// ALOGW("unexpected whence %d\n", whence);
+// // this was happening due to an off64_t size mismatch
+// assert(false);
+// return (off64_t) -1;
+// }
+// if (newOffset < 0 || newOffset > maxPosn) {
+// ALOGW("seek out of range: want %ld, end=%ld\n",
+// (long) newOffset, (long) maxPosn);
+// return (off64_t) -1;
+// }
+// return newOffset;
+//}
+///*
+// * ===========================================================================
+// * _FileAsset
+// * ===========================================================================
+// */
+///*
+// * Constructor.
+// */
+//_FileAsset::_FileAsset(void)
+// : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
+//{
+// // Register the Asset with the global list here after it is fully constructed and its
+// // vtable pointer points to this concrete type. b/31113965
+// registerAsset(this);
+//}
+///*
+// * Destructor. Release resources.
+// */
+//_FileAsset::~_FileAsset(void)
+//{
+// close();
+// // Unregister the Asset from the global list here before it is destructed and while its vtable
+// // pointer still points to this concrete type. b/31113965
+// unregisterAsset(this);
+//}
+///*
+// * Operate on a chunk of an uncompressed file.
+// *
+// * Zero-length chunks are allowed.
+// */
+//status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
+//{
+// assert(mFp == NULL); // no reopen
+// assert(mMap == NULL);
+// assert(fd >= 0);
+// assert(offset >= 0);
+// /*
+// * Seek to end to get file length.
+// */
+// off64_t fileLength;
+// fileLength = lseek64(fd, 0, SEEK_END);
+// if (fileLength == (off64_t) -1) {
+// // probably a bad file descriptor
+// ALOGD("failed lseek (errno=%d)\n", errno);
+// return UNKNOWN_ERROR;
+// }
+// if ((off64_t) (offset + length) > fileLength) {
+// ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
+// (long) offset, (long) length, (long) fileLength);
+// return BAD_INDEX;
+// }
+// /* after fdopen, the fd will be closed on fclose() */
+// mFp = fdopen(fd, "rb");
+// if (mFp == NULL)
+// return UNKNOWN_ERROR;
+// mStart = offset;
+// mLength = length;
+// assert(mOffset == 0);
+// /* seek the FILE* to the start of chunk */
+// if (fseek(mFp, mStart, SEEK_SET) != 0) {
+// assert(false);
+// }
+// mFileName = fileName != NULL ? strdup(fileName) : NULL;
+// return NO_ERROR;
+//}
+///*
+// * Create the chunk from the map.
+// */
+//status_t _FileAsset::openChunk(FileMap* dataMap)
+//{
+// assert(mFp == NULL); // no reopen
+// assert(mMap == NULL);
+// assert(dataMap != NULL);
+// mMap = dataMap;
+// mStart = -1; // not used
+// mLength = dataMap->getDataLength();
+// assert(mOffset == 0);
+// return NO_ERROR;
+//}
+///*
+// * Read a chunk of data.
+// */
+//ssize_t _FileAsset::read(void* buf, size_t count)
+//{
+// size_t maxLen;
+// size_t actual;
+// assert(mOffset >= 0 && mOffset <= mLength);
+// if (getAccessMode() == ACCESS_BUFFER) {
+// /*
+// * On first access, read or map the entire file. The caller has
+// * requested buffer access, either because they're going to be
+// * using the buffer or because what they're doing has appropriate
+// * performance needs and access patterns.
+// */
+// if (mBuf == NULL)
+// getBuffer(false);
+// }
+// /* adjust count if we're near EOF */
+// maxLen = mLength - mOffset;
+// if (count > maxLen)
+// count = maxLen;
+// if (!count)
+// return 0;
+// if (mMap != NULL) {
+// /* copy from mapped area */
+// //printf("map read\n");
+// memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
+// actual = count;
+// } else if (mBuf != NULL) {
+// /* copy from buffer */
+// //printf("buf read\n");
+// memcpy(buf, (char*)mBuf + mOffset, count);
+// actual = count;
+// } else {
+// /* read from the file */
+// //printf("file read\n");
+// if (ftell(mFp) != mStart + mOffset) {
+// ALOGE("Hosed: %ld != %ld+%ld\n",
+// ftell(mFp), (long) mStart, (long) mOffset);
+// assert(false);
+// }
+// /*
+// * This returns 0 on error or eof. We need to use ferror() or feof()
+// * to tell the difference, but we don't currently have those on the
+// * device. However, we know how much data is *supposed* to be in the
+// * file, so if we don't read the full amount we know something is
+// * hosed.
+// */
+// actual = fread(buf, 1, count, mFp);
+// if (actual == 0) // something failed -- I/O error?
+// return -1;
+// assert(actual == count);
+// }
+// mOffset += actual;
+// return actual;
+//}
+///*
+// * Seek to a new position.
+// */
+//off64_t _FileAsset::seek(off64_t offset, int whence)
+//{
+// off64_t newPosn;
+// off64_t actualOffset;
+// // compute new position within chunk
+// newPosn = handleSeek(offset, whence, mOffset, mLength);
+// if (newPosn == (off64_t) -1)
+// return newPosn;
+// actualOffset = mStart + newPosn;
+// if (mFp != NULL) {
+// if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
+// return (off64_t) -1;
+// }
+// mOffset = actualOffset - mStart;
+// return mOffset;
+//}
+///*
+// * Close the asset.
+// */
+//void _FileAsset::close(void)
+//{
+// if (mMap != NULL) {
+// delete mMap;
+// mMap = NULL;
+// }
+// if (mBuf != NULL) {
+// delete[] mBuf;
+// mBuf = NULL;
+// }
+// if (mFileName != NULL) {
+// free(mFileName);
+// mFileName = NULL;
+// }
+// if (mFp != NULL) {
+// // can only be NULL when called from destructor
+// // (otherwise we would never return this object)
+// fclose(mFp);
+// mFp = NULL;
+// }
+//}
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/ResStringPool.java b/resources/src/main/java/org/robolectric/res/android/ResStringPool.java
index 0ccbf6a..8cb48f8 100644
--- a/resources/src/main/java/org/robolectric/res/android/ResStringPool.java
+++ b/resources/src/main/java/org/robolectric/res/android/ResStringPool.java
@@ -8,9 +8,11 @@
import static org.robolectric.res.android.Errors.NO_ERROR;
import static org.robolectric.res.android.Errors.NO_INIT;
import static org.robolectric.res.android.Util.ALOGI;
-import static org.robolectric.res.android.Util.ALOGW;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import org.robolectric.res.android.ResXMLTree.XmlBuffer.XmlResStringPool;
import org.robolectric.util.Strings;
/**
@@ -46,7 +48,7 @@
setTo(header, strings);
}
- private int setTo(ResStringPoolHeader header, List<String> strings) {
+ public int setTo(ResStringPoolHeader header, List<String> strings) {
if (header == null || strings == null || strings.isEmpty()) {
return setError(BAD_TYPE);
}
@@ -55,6 +57,12 @@
return setError(NO_ERROR);
}
+// public void setTo(XmlResStringPool xmlStringPool) {
+// this.mHeader = new ResStringPoolHeader();
+// this.mStrings = new ArrayList<>();
+// Collections.addAll(mStrings, xmlStringPool.strings());
+// }
+
private int setError(int error) {
mError = error;
return mError;
@@ -75,7 +83,18 @@
return mStrings.get(idx);
}
return null;
+ }
+ String stringAt(int idx, Ref<Integer> outLen) {
+ String s = stringAt(idx);
+ if (s != null && outLen != null) {
+ outLen.set(s.length());
+ }
+ return s;
+ }
+
+ public String string8At(int id, Ref<Integer> outLen) {
+ return stringAt(id, outLen);
}
// final ResStringPool_span* styleAt(final ResStringPool_ref& ref) final;
@@ -140,6 +159,14 @@
return mStrings.size();
}
+ public boolean isUTF8() {
+ return true;
+ }
+
+ public int getError() {
+ return mError;
+ }
+
// int styleCount() final;
// int bytes() final;
//
diff --git a/resources/src/main/java/org/robolectric/res/android/ResTable.java b/resources/src/main/java/org/robolectric/res/android/ResTable.java
index 1181bec..0a5d1d1 100644
--- a/resources/src/main/java/org/robolectric/res/android/ResTable.java
+++ b/resources/src/main/java/org/robolectric/res/android/ResTable.java
@@ -37,16 +37,16 @@
static final int APP_PACKAGE_ID = 0x7f;
static final int SYS_PACKAGE_ID = 0x01;
- private static final boolean kDebugStringPoolNoisy = false;
- private static final boolean kDebugXMLNoisy = false;
- private static final boolean kDebugTableNoisy = false;
- private static final boolean kDebugTableGetEntry = false;
+ 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;
- private static final boolean kDebugLoadTableNoisy = false;
- private static final boolean kDebugLoadTableSuperNoisy = false;
- private static final boolean kDebugTableTheme = false;
- private static final boolean kDebugResXMLTree = false;
- private static final boolean kDebugLibNoisy = 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);
@@ -101,6 +101,8 @@
byte[] buf = ByteStreams.toByteArray(is);
ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
Chunk.read(buffer, this, cookie);
+// int id = mPackageGroups.values().iterator().next().id; // huh?
+// mPackageMap[id] = (byte) (id + 1); // huh?
}
// Errors add(final Object data, int size, final int cookie, boolean copyData) {
@@ -825,6 +827,18 @@
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;
+ }
+
// 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.
diff --git a/resources/src/main/java/org/robolectric/res/android/ResValue.java b/resources/src/main/java/org/robolectric/res/android/ResValue.java
index 6f45a3c..fb921a2 100644
--- a/resources/src/main/java/org/robolectric/res/android/ResValue.java
+++ b/resources/src/main/java/org/robolectric/res/android/ResValue.java
@@ -41,4 +41,9 @@
.append("}");
return sb.toString();
}
+
+ public void copyFrom_dtoh(ResValue resValue) {
+ this.dataType = resValue.dataType;
+ this.data = resValue.data;
+ }
}
diff --git a/resources/src/main/java/org/robolectric/res/android/ResXMLParser.java b/resources/src/main/java/org/robolectric/res/android/ResXMLParser.java
new file mode 100644
index 0000000..bc353a2
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ResXMLParser.java
@@ -0,0 +1,609 @@
+package org.robolectric.res.android;
+
+import static org.robolectric.res.android.Errors.BAD_TYPE;
+import static org.robolectric.res.android.Errors.NAME_NOT_FOUND;
+import static org.robolectric.res.android.Errors.NO_ERROR;
+import static org.robolectric.res.android.ResTable.kDebugStringPoolNoisy;
+import static org.robolectric.res.android.ResTable.kDebugXMLNoisy;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.BAD_DOCUMENT;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.END_DOCUMENT;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.END_NAMESPACE;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.END_TAG;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.FIRST_CHUNK_CODE;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.START_DOCUMENT;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.START_NAMESPACE;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.START_TAG;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.TEXT;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_CDATA_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_END_ELEMENT_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_END_NAMESPACE_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_FIRST_CHUNK_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_START_ELEMENT_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_START_NAMESPACE_TYPE;
+import static org.robolectric.res.android.Util.ALOGI;
+import static org.robolectric.res.android.Util.ALOGW;
+import static org.robolectric.res.android.Util.dtohl;
+import static org.robolectric.res.android.Util.dtohs;
+import static org.robolectric.res.android.Util.isTruthy;
+
+import org.robolectric.res.android.ResourceTypes.ResChunk_header;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_attrExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_attribute;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_cdataExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_endElementExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_namespaceExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_node;
+
+public class ResXMLParser {
+
+ static final int SIZEOF_RESVALUE = 8;
+ static final int SIZEOF_RESXMLTREE_NAMESPACE_EXT = 4;
+ static final int SIZEOF_RESXMLTREE_NODE = ResChunk_header.SIZEOF + 8;
+ static final int SIZEOF_RESXMLTREE_ATTR_EXT = 20;
+ static final int SIZEOF_RESXMLTREE_ATTRIBUTE = 12 + SIZEOF_RESVALUE;
+ static final int SIZEOF_RESXMLTREE_END_ELEMENT_EXT = 8;
+ static final int SIZEOF_RESXMLTREE_CDATA_EXT = 4 + SIZEOF_RESVALUE;
+ static final int SIZEOF_CHAR = 2;
+
+ public static class event_code_t {
+ public static final int BAD_DOCUMENT = -1;
+ public static final int START_DOCUMENT = 0;
+ public static final int END_DOCUMENT = 1;
+
+ public static final int FIRST_CHUNK_CODE = RES_XML_FIRST_CHUNK_TYPE;
+
+ public static final int START_NAMESPACE = RES_XML_START_NAMESPACE_TYPE;
+ public static final int END_NAMESPACE = RES_XML_END_NAMESPACE_TYPE;
+ public static final int START_TAG = RES_XML_START_ELEMENT_TYPE;
+ public static final int END_TAG = RES_XML_END_ELEMENT_TYPE;
+ public static final int TEXT = RES_XML_CDATA_TYPE;
+ }
+
+ ResXMLTree mTree;
+ int mEventCode;
+ ResXMLTree_node mCurNode;
+ Object mCurExt;
+
+ public ResXMLParser(ResXMLTree tree) {
+ this.mTree = tree;
+ this.mEventCode = BAD_DOCUMENT;
+ }
+
+ public void restart() {
+ mCurNode = null;
+ mEventCode = mTree.mError == NO_ERROR ? START_DOCUMENT : BAD_DOCUMENT;
+ }
+
+ public ResStringPool getStrings() {
+ return mTree.mStrings;
+ }
+
+ int getEventType()
+ {
+ return mEventCode;
+ }
+
+ public int next()
+ {
+ if (mEventCode == START_DOCUMENT) {
+ mCurNode = mTree.mRootNode;
+ mCurExt = mTree.mRootExt;
+ return (mEventCode=mTree.mRootCode);
+ } else if (mEventCode >= FIRST_CHUNK_CODE) {
+ return nextNode();
+ }
+ return mEventCode;
+ }
+
+ int getCommentID()
+ {
+ return mCurNode != null ? dtohl(mCurNode.comment().index()) : -1;
+ }
+
+final String getComment(Ref<Integer> outLen)
+ {
+ int id = getCommentID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+ }
+
+ public int getLineNumber()
+ {
+ return mCurNode != null ? dtohl(mCurNode.lineNumber()) : -1;
+ }
+
+ public int getTextID()
+ {
+ if (mEventCode == TEXT) {
+ return dtohl(((ResXMLTree_cdataExt)mCurExt).data().index());
+ }
+ return -1;
+ }
+
+final String getText(Ref<Integer> outLen)
+ {
+ int id = getTextID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+ }
+
+ int getTextValue(ResValue outValue)
+ {
+ if (mEventCode == TEXT) {
+ outValue.copyFrom_dtoh(((ResXMLTree_cdataExt)mCurExt).typedData());
+ return SIZEOF_RESVALUE /* sizeof(Res_value) */;
+ }
+ return BAD_TYPE;
+ }
+
+ int getNamespacePrefixID()
+ {
+ if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) {
+ return dtohl(((ResXMLTree_namespaceExt)mCurExt).prefix().index());
+ }
+ return -1;
+ }
+
+final String getNamespacePrefix(Ref<Integer> outLen)
+ {
+ int id = getNamespacePrefixID();
+ //printf("prefix=%d event=%p\n", id, mEventCode);
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+ }
+
+ int getNamespaceUriID()
+ {
+ if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) {
+ return dtohl(((ResXMLTree_namespaceExt)mCurExt).uri().index());
+ }
+ return -1;
+ }
+
+final String getNamespaceUri(Ref<Integer> outLen)
+ {
+ int id = getNamespaceUriID();
+ //printf("uri=%d event=%p\n", id, mEventCode);
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+ }
+
+ int getElementNamespaceID()
+ {
+ if (mEventCode == START_TAG) {
+ return dtohl(((ResXMLTree_attrExt)mCurExt).ns().index());
+ }
+ if (mEventCode == END_TAG) {
+ return dtohl(((ResXMLTree_endElementExt)mCurExt).ns().index());
+ }
+ return -1;
+ }
+
+final String getElementNamespace(Ref<Integer> outLen)
+ {
+ int id = getElementNamespaceID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+ }
+
+ public int getElementNameID()
+ {
+ if (mEventCode == START_TAG) {
+ return dtohl(((ResXMLTree_attrExt)mCurExt).name().index());
+ }
+ if (mEventCode == END_TAG) {
+ return dtohl(((ResXMLTree_endElementExt)mCurExt).name().index());
+ }
+ return -1;
+ }
+
+final String getElementName(Ref<Integer> outLen)
+ {
+ int id = getElementNameID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+ }
+
+ public int getAttributeCount()
+ {
+ if (mEventCode == START_TAG) {
+ return dtohs(((ResXMLTree_attrExt)mCurExt).attributeCount());
+ }
+ return 0;
+ }
+
+ public int getAttributeNamespaceID(int idx)
+ {
+ if (mEventCode == START_TAG) {
+ ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+ if (idx < dtohs(tag.attributeCount())) {
+// final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+// (((final int8_t*)tag)
+// + dtohs(tag.attributeStart())
+// + (dtohs(tag.attributeSize())*idx));
+ ResXMLTree_attribute attr = tag.attributeAt(idx);
+ return dtohl(attr.ns().index());
+ }
+ }
+ return -2;
+ }
+
+final String getAttributeNamespace(int idx, Ref<Integer> outLen)
+ {
+ int id = getAttributeNamespaceID(idx);
+ //printf("attribute namespace=%d idx=%d event=%p\n", id, idx, mEventCode);
+ if (kDebugXMLNoisy) {
+ System.out.println(String.format("getAttributeNamespace 0x%zx=0x%x\n", idx, id));
+ }
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+ }
+
+final String getAttributeNamespace8(int idx, Ref<Integer> outLen)
+ {
+ int id = getAttributeNamespaceID(idx);
+ //printf("attribute namespace=%d idx=%d event=%p\n", id, idx, mEventCode);
+ if (kDebugXMLNoisy) {
+ System.out.println(String.format("getAttributeNamespace 0x%zx=0x%x\n", idx, id));
+ }
+ return id >= 0 ? mTree.mStrings.string8At(id, outLen) : null;
+ }
+
+ public int getAttributeNameID(int idx)
+ {
+ if (mEventCode == START_TAG) {
+ ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+ if (idx < dtohs(tag.attributeCount())) {
+// final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+// (((final int8_t*)tag)
+// + dtohs(tag.attributeStart())
+// + (dtohs(tag.attributeSize())*idx));
+ ResXMLTree_attribute attr = tag.attributeAt(idx);
+ return dtohl(attr.name().index());
+ }
+ }
+ return -1;
+ }
+
+final String getAttributeName(int idx, Ref<Integer> outLen)
+ {
+ int id = getAttributeNameID(idx);
+ //printf("attribute name=%d idx=%d event=%p\n", id, idx, mEventCode);
+ if (kDebugXMLNoisy) {
+ System.out.println(String.format("getAttributeName 0x%zx=0x%x\n", idx, id));
+ }
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+ }
+
+final String getAttributeName8(int idx, Ref<Integer> outLen)
+ {
+ int id = getAttributeNameID(idx);
+ //printf("attribute name=%d idx=%d event=%p\n", id, idx, mEventCode);
+ if (kDebugXMLNoisy) {
+ System.out.println(String.format("getAttributeName 0x%zx=0x%x\n", idx, id));
+ }
+ return id >= 0 ? mTree.mStrings.string8At(id, outLen) : null;
+ }
+
+ int getAttributeNameResID(int idx)
+ {
+ int id = getAttributeNameID(idx);
+ if (id >= 0 && (int)id < mTree.mNumResIds) {
+ int resId = dtohl(mTree.mResIds[id]);
+ if (mTree.mDynamicRefTable != null) {
+ Ref<Integer> resIdRef = new Ref<>(resId);
+ mTree.mDynamicRefTable.lookupResourceId(resIdRef);
+ resId = resIdRef.get();
+ }
+ return resId;
+ }
+ return 0;
+ }
+
+ int getAttributeValueStringID(int idx)
+ {
+ if (mEventCode == START_TAG) {
+ ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+ if (idx < dtohs(tag.attributeCount())) {
+// final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+// (((final int8_t*)tag)
+// + dtohs(tag.attributeStart())
+// + (dtohs(tag.attributeSize())*idx));
+ ResXMLTree_attribute attr = tag.attributeAt(idx);
+ return dtohl(attr.rawValue().index());
+ }
+ }
+ return -1;
+ }
+
+final String getAttributeStringValue(int idx, Ref<Integer> outLen)
+ {
+ int id = getAttributeValueStringID(idx);
+ if (kDebugXMLNoisy) {
+ System.out.println(String.format("getAttributeValue 0x%zx=0x%x\n", idx, id));
+ }
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+ }
+
+ int getAttributeDataType(int idx)
+ {
+ if (mEventCode == START_TAG) {
+ final ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+ if (idx < dtohs(tag.attributeCount())) {
+// final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+// (((final int8_t*)tag)
+// + dtohs(tag.attributeStart())
+// + (dtohs(tag.attributeSize())*idx));
+ ResXMLTree_attribute attr = tag.attributeAt(idx);
+ int type = attr.typedValue().dataType;
+ if (type != DataType.DYNAMIC_REFERENCE.code()) {
+ return type;
+ }
+
+ // This is a dynamic reference. We adjust those references
+ // to regular references at this level, so lie to the caller.
+ return DataType.REFERENCE.code();
+ }
+ }
+ return DataType.NULL.code();
+ }
+
+ int getAttributeData(int idx)
+ {
+ if (mEventCode == START_TAG) {
+ ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+ if (idx < dtohs(tag.attributeCount())) {
+// final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+// (((final int8_t*)tag)
+// + dtohs(tag.attributeStart())
+// + (dtohs(tag.attributeSize())*idx));
+ ResXMLTree_attribute attr = tag.attributeAt(idx);
+ if (attr.typedValue().dataType != DataType.DYNAMIC_REFERENCE.code() ||
+ mTree.mDynamicRefTable == null) {
+ return dtohl(attr.typedValue().data);
+ }
+
+ Ref<Integer> data = new Ref<>(dtohl(attr.typedValue().data));
+ if (mTree.mDynamicRefTable.lookupResourceId(data) == NO_ERROR) {
+ return data.get();
+ }
+ }
+ }
+ return 0;
+ }
+
+ public int getAttributeValue(int idx, ResValue outValue)
+ {
+ if (mEventCode == START_TAG) {
+ ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+ if (idx < dtohs(tag.attributeCount())) {
+// final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+// (((final int8_t*)tag)
+// + dtohs(tag.attributeStart())
+// + (dtohs(tag.attributeSize())*idx));
+ ResXMLTree_attribute attr = tag.attributeAt(idx);
+ outValue.copyFrom_dtoh(attr.typedValue());
+ if (mTree.mDynamicRefTable != null &&
+ mTree.mDynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) {
+ return BAD_TYPE;
+ }
+ return SIZEOF_RESVALUE /* sizeof(ResValue) */;
+ }
+ }
+ return BAD_TYPE;
+ }
+
+ int indexOfAttribute(final String ns, final String attr)
+ {
+ String nsStr = ns != null ? ns : "";
+ String attrStr = attr;
+ return indexOfAttribute(isTruthy(ns) ? nsStr : null, isTruthy(ns) ? nsStr.length() : 0,
+ attrStr, attrStr.length());
+ }
+
+ int indexOfAttribute(final String ns, int nsLen,
+ final String attr, int attrLen)
+ {
+ if (mEventCode == START_TAG) {
+ if (attr == null) {
+ return NAME_NOT_FOUND;
+ }
+ final int N = getAttributeCount();
+ if (mTree.mStrings.isUTF8()) {
+ String8 ns8 = null, attr8;
+ if (ns != null) {
+ ns8 = new String8(ns, nsLen);
+ }
+ attr8 = new String8(attr, attrLen);
+ if (kDebugStringPoolNoisy) {
+ ALOGI("indexOfAttribute UTF8 %s (%zu) / %s (%zu)", ns8.string(), nsLen,
+ attr8.string(), attrLen);
+ }
+ for (int i=0; i<N; i++) {
+ Ref<Integer> curNsLen = new Ref<>(0), curAttrLen = new Ref<>(0);
+ final String curNs = getAttributeNamespace8(i, curNsLen);
+ final String curAttr = getAttributeName8(i, curAttrLen);
+ if (kDebugStringPoolNoisy) {
+ ALOGI(" curNs=%s (%zu), curAttr=%s (%zu)", curNs, curNsLen, curAttr, curAttrLen);
+ }
+ if (curAttr != null && curNsLen.get() == nsLen && curAttrLen.get() == attrLen
+ && memcmp(attr8.string(), curAttr, attrLen) == 0) {
+ if (ns == null) {
+ if (curNs == null) {
+ if (kDebugStringPoolNoisy) {
+ ALOGI(" FOUND!");
+ }
+ return i;
+ }
+ } else if (curNs != null) {
+ //printf(" -. ns=%s, curNs=%s\n",
+ // String8(ns).string(), String8(curNs).string());
+ if (memcmp(ns8.string(), curNs, nsLen) == 0) {
+ if (kDebugStringPoolNoisy) {
+ ALOGI(" FOUND!");
+ }
+ return i;
+ }
+ }
+ }
+ }
+ } else {
+ if (kDebugStringPoolNoisy) {
+ ALOGI("indexOfAttribute UTF16 %s (%zu) / %s (%zu)",
+ ns /*String8(ns, nsLen).string()*/, nsLen,
+ attr /*String8(attr, attrLen).string()*/, attrLen);
+ }
+ for (int i=0; i<N; i++) {
+ Ref<Integer> curNsLen = new Ref<>(0), curAttrLen = new Ref<>(0);
+ final String curNs = getAttributeNamespace(i, curNsLen);
+ final String curAttr = getAttributeName(i, curAttrLen);
+ if (kDebugStringPoolNoisy) {
+ ALOGI(" curNs=%s (%zu), curAttr=%s (%zu)",
+ curNs /*String8(curNs, curNsLen).string()*/, curNsLen,
+ curAttr /*String8(curAttr, curAttrLen).string()*/, curAttrLen);
+ }
+ if (curAttr != null && curNsLen.get() == nsLen && curAttrLen.get() == attrLen
+ && (memcmp(attr, curAttr, attrLen*SIZEOF_CHAR/*sizeof(char16_t)*/) == 0)) {
+ if (ns == null) {
+ if (curNs == null) {
+ if (kDebugStringPoolNoisy) {
+ ALOGI(" FOUND!");
+ }
+ return i;
+ }
+ } else if (curNs != null) {
+ //printf(" -. ns=%s, curNs=%s\n",
+ // String8(ns).string(), String8(curNs).string());
+ if (memcmp(ns, curNs, nsLen*SIZEOF_CHAR/*sizeof(char16_t)*/) == 0) {
+ if (kDebugStringPoolNoisy) {
+ ALOGI(" FOUND!");
+ }
+ return i;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return NAME_NOT_FOUND;
+ }
+
+ private int memcmp(String s1, String s2, int len) {
+ for (int i = 0; i < len; i++) {
+ int d = s1.charAt(i) - s2.charAt(i);
+ if (d != 0) {
+ return d;
+ }
+ }
+ return 0;
+ }
+
+ int indexOfID()
+ {
+ if (mEventCode == START_TAG) {
+ final int idx = dtohs(((ResXMLTree_attrExt)mCurExt).idIndex());
+ if (idx > 0) return (idx-1);
+ }
+ return NAME_NOT_FOUND;
+ }
+
+ int indexOfClass()
+ {
+ if (mEventCode == START_TAG) {
+ final int idx = dtohs(((ResXMLTree_attrExt)mCurExt).classIndex());
+ if (idx > 0) return (idx-1);
+ }
+ return NAME_NOT_FOUND;
+ }
+
+ public int indexOfStyle()
+ {
+ if (mEventCode == START_TAG) {
+ final int idx = dtohs(((ResXMLTree_attrExt)mCurExt).styleIndex());
+ if (idx > 0) return (idx-1);
+ }
+ return NAME_NOT_FOUND;
+ }
+
+ int nextNode()
+ {
+ if (mEventCode < 0) {
+ return mEventCode;
+ }
+
+ do {
+// final ResXMLTree_node next = (ResXMLTree_node)
+// (((final int8_t*)mCurNode) + dtohl(mCurNode.header().size()));
+ ResXMLTree_node next = mTree.mBuffer.new XmlTreeNode(mCurNode.myOffset() + dtohl(mCurNode.header().size()));
+ if (kDebugXMLNoisy) {
+ ALOGI("Next node: prev=%p, next=%p\n", mCurNode, next);
+ }
+
+ if (next.myOffset() >= mTree.mDataLen) {
+ mCurNode = null;
+ return (mEventCode=END_DOCUMENT);
+ }
+
+ if (mTree.validateNode(next) != NO_ERROR) {
+ mCurNode = null;
+ return (mEventCode=BAD_DOCUMENT);
+ }
+
+ mCurNode = next;
+ final int headerSize = dtohs(next.header().headerSize());
+ final int totalSize = dtohl(next.header().size());
+ mCurExt = next.myOffset() + headerSize;
+ int minExtSize = 0;
+ int eventCode = dtohs(next.header().type());
+ switch ((mEventCode=eventCode)) {
+ case RES_XML_START_NAMESPACE_TYPE:
+ case RES_XML_END_NAMESPACE_TYPE:
+ minExtSize = SIZEOF_RESXMLTREE_NAMESPACE_EXT /*sizeof(ResXMLTree_namespaceExt)*/;
+ break;
+ case RES_XML_START_ELEMENT_TYPE:
+ minExtSize = SIZEOF_RESXMLTREE_ATTR_EXT /*sizeof(ResXMLTree_attrExt)*/;
+ break;
+ case RES_XML_END_ELEMENT_TYPE:
+ minExtSize = SIZEOF_RESXMLTREE_END_ELEMENT_EXT /*sizeof(ResXMLTree_endElementExt)*/;
+ break;
+ case RES_XML_CDATA_TYPE:
+ minExtSize = SIZEOF_RESXMLTREE_CDATA_EXT /*sizeof(ResXMLTree_cdataExt)*/;
+ break;
+ default:
+ ALOGW("Unknown XML block: header type %d in node at %d\n",
+ (int)dtohs(next.header().type()),
+ (next.myOffset()-mTree.mHeader.myOffset()));
+ continue;
+ }
+
+ if ((totalSize-headerSize) < minExtSize) {
+ ALOGW("Bad XML block: header type 0x%x in node at 0x%x has size %d, need %d\n",
+ (int)dtohs(next.header().type()),
+ (next.myOffset()-mTree.mHeader.myOffset()),
+ (int)(totalSize-headerSize), (int)minExtSize);
+ return (mEventCode=BAD_DOCUMENT);
+ }
+
+ //printf("CurNode=%p, CurExt=%p, headerSize=%d, minExtSize=%d\n",
+ // mCurNode, mCurExt, headerSize, minExtSize);
+
+ return eventCode;
+ } while (true);
+ }
+
+ void getPosition(ResXMLPosition pos)
+ {
+ pos.eventCode = mEventCode;
+ pos.curNode = mCurNode;
+ pos.curExt = mCurExt;
+ }
+
+ void setPosition(final ResXMLPosition pos)
+ {
+ mEventCode = pos.eventCode;
+ mCurNode = pos.curNode;
+ mCurExt = pos.curExt;
+ }
+
+ static class ResXMLPosition
+ {
+ int eventCode;
+ ResXMLTree_node curNode;
+ Object curExt;
+ };
+
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/ResXMLTree.java b/resources/src/main/java/org/robolectric/res/android/ResXMLTree.java
new file mode 100644
index 0000000..dda75cc
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ResXMLTree.java
@@ -0,0 +1,559 @@
+package org.robolectric.res.android;
+
+import static org.robolectric.res.android.Errors.BAD_TYPE;
+import static org.robolectric.res.android.Errors.NO_ERROR;
+import static org.robolectric.res.android.Errors.NO_INIT;
+import static org.robolectric.res.android.ResTable.kDebugResXMLTree;
+import static org.robolectric.res.android.ResTable.kDebugXMLNoisy;
+import static org.robolectric.res.android.ResXMLParser.SIZEOF_RESXMLTREE_ATTR_EXT;
+import static org.robolectric.res.android.ResXMLParser.SIZEOF_RESXMLTREE_NODE;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.BAD_DOCUMENT;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.START_DOCUMENT;
+import static org.robolectric.res.android.ResourceTypes.RES_STRING_POOL_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_FIRST_CHUNK_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_LAST_CHUNK_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_RESOURCE_MAP_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_START_ELEMENT_TYPE;
+import static org.robolectric.res.android.ResourceTypes.validate_chunk;
+import static org.robolectric.res.android.Util.ALOGI;
+import static org.robolectric.res.android.Util.ALOGW;
+import static org.robolectric.res.android.Util.SIZEOF_INT;
+import static org.robolectric.res.android.Util.dtohl;
+import static org.robolectric.res.android.Util.dtohs;
+import static org.robolectric.res.android.Util.isTruthy;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.robolectric.res.android.Chunk.StringPoolChunk;
+import org.robolectric.res.android.ResXMLTree.XmlBuffer.XmlTreeHeader;
+import org.robolectric.res.android.ResourceTypes.ResChunk_header;
+import org.robolectric.res.android.ResourceTypes.ResStringPool_header;
+import org.robolectric.res.android.ResourceTypes.ResStringPool_ref;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_attrExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_attribute;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_header;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_node;
+
+public class ResXMLTree {
+
+ final DynamicRefTable mDynamicRefTable;
+ public final ResXMLParser mParser;
+
+ int mError;
+ byte[] mOwnedData;
+ XmlBuffer mBuffer;
+ XmlTreeHeader mHeader;
+ int mSize;
+ // final uint8_t* mDataEnd;
+ int mDataLen;
+ ResStringPool mStrings = new ResStringPool();
+ int[] mResIds;
+ int mNumResIds;
+ ResXMLTree_node mRootNode;
+ Object mRootExt;
+ int mRootCode;
+
+ static volatile AtomicInteger gCount = new AtomicInteger(0);
+
+ public ResXMLTree(DynamicRefTable dynamicRefTable) {
+ mParser = new ResXMLParser(this);
+
+ mDynamicRefTable = dynamicRefTable;
+ mError = NO_INIT;
+ mOwnedData = null;
+
+ if (kDebugResXMLTree) {
+ ALOGI("Creating ResXMLTree %p #%d\n", this, gCount.getAndIncrement()+1);
+ }
+ mParser.restart();
+ }
+
+// ResXMLTree() {
+// this(null);
+// }
+
+// ~ResXMLTree()
+// {
+ protected void finalize() {
+ if (kDebugResXMLTree) {
+ ALOGI("Destroying ResXMLTree in %p #%d\n", this, gCount.getAndDecrement()-1);
+ }
+ uninit();
+ }
+
+ public int setTo(byte[] data, int size, boolean copyData)
+ {
+ uninit();
+ mParser.mEventCode = START_DOCUMENT;
+
+ if (!isTruthy(data) || !isTruthy(size)) {
+ return (mError=BAD_TYPE);
+ }
+
+ if (copyData) {
+ mOwnedData = new byte[size];
+// if (mOwnedData == null) {
+// return (mError=NO_MEMORY);
+// }
+// memcpy(mOwnedData, data, size);
+ System.arraycopy(data, 0, mOwnedData, 0, size);
+ data = mOwnedData;
+ }
+
+ mBuffer = new XmlBuffer(data);
+ mHeader = mBuffer.new XmlTreeHeader(0);
+ mSize = dtohl(mHeader.header().size());
+ if (dtohs(mHeader.header().headerSize()) > mSize || mSize > size) {
+ ALOGW("Bad XML block: header size %d or total size %d is larger than data size %d\n",
+ (int)dtohs(mHeader.header().headerSize()),
+ (int)dtohl(mHeader.header().size()), (int)size);
+ mError = BAD_TYPE;
+ mParser.restart();
+ return mError;
+ }
+// mDataEnd = ((final uint8_t*)mHeader) + mSize;
+ mDataLen = mSize;
+
+ mStrings.uninit();
+ mRootNode = null;
+ mResIds = null;
+ mNumResIds = 0;
+
+ // First look for a couple interesting chunks: the string block
+ // and first XML node.
+ ResChunk_header chunk =
+// (final ResChunk_header*)(((final uint8_t*)mHeader) + dtohs(mHeader.header.headerSize));
+ mBuffer.new ChunkHeader(mHeader.header().headerSize());
+
+ ResChunk_header lastChunk = chunk;
+ while (chunk.myOffset() /*((final uint8_t*)chunk)*/ < (mDataLen- ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/) &&
+ chunk.myOffset() /*((final uint8_t*)chunk)*/ < (mDataLen-dtohl(chunk.size()))) {
+ int err = validate_chunk(chunk, ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/, mDataLen, "XML");
+ if (err != NO_ERROR) {
+ mError = err;
+// goto done;
+ mParser.restart();
+ return mError;
+ }
+ final short type = dtohs(chunk.type());
+ final int size1 = dtohl(chunk.size());
+ if (kDebugXMLNoisy) {
+// System.out.println(String.format("Scanning @ %p: type=0x%x, size=0x%zx\n",
+// (void*)(((uintptr_t)chunk)-((uintptr_t)mHeader)), type, size1);
+ }
+ if (type == RES_STRING_POOL_TYPE) {
+ // todo: merge Chunk/interface buffer reading strategies
+ ResChunkHeader resChunkHeader = new ResChunkHeader();
+ resChunkHeader.type = chunk.type();
+ resChunkHeader.headerSize = chunk.headerSize();
+ resChunkHeader.size = chunk.size();
+ StringPoolChunk stringPoolChunk = new StringPoolChunk(mBuffer.byteBuffer, chunk.myOffset(),
+ resChunkHeader);
+// mStrings.setTo(mBuffer.new XmlResStringPool(chunk.myOffset()));
+ int stringCount = stringPoolChunk.getStringCount();
+ List<String> stringEntries = new ArrayList<>(stringCount);
+ for (int i=0; i < stringCount; i++) {
+ stringEntries.add(stringPoolChunk.getString(i));
+ }
+
+ ResStringPoolHeader header = new ResStringPoolHeader();
+ header.flags = stringPoolChunk.getFlags();
+ header.stringCount = stringPoolChunk.getStringCount();
+ mStrings.setTo(header, stringEntries);
+ } else if (type == RES_XML_RESOURCE_MAP_TYPE) {
+// mResIds = (final int*)
+// (((final uint8_t*)chunk)+dtohs(chunk.headerSize()));
+ mNumResIds = (dtohl(chunk.size())-dtohs(chunk.headerSize()))/SIZEOF_INT /*sizeof(int)*/;
+ mResIds = new int[mNumResIds];
+ for (int i = 0; i < mNumResIds; i++) {
+ mResIds[i] = mBuffer.byteBuffer.getInt(chunk.myOffset() + chunk.headerSize() + i * SIZEOF_INT);
+ }
+ } else if (type >= RES_XML_FIRST_CHUNK_TYPE
+ && type <= RES_XML_LAST_CHUNK_TYPE) {
+ if (validateNode(mBuffer.new XmlTreeNode(chunk.myOffset())) != NO_ERROR) {
+ mError = BAD_TYPE;
+// goto done;
+ mParser.restart();
+ return mError;
+ }
+ mParser.mCurNode = mBuffer.new XmlTreeNode(lastChunk.myOffset());
+ if (mParser.nextNode() == BAD_DOCUMENT) {
+ mError = BAD_TYPE;
+// goto done;
+ mParser.restart();
+ return mError;
+ }
+ mRootNode = mParser.mCurNode;
+ mRootExt = mParser.mCurExt;
+ mRootCode = mParser.mEventCode;
+ break;
+ } else {
+ if (kDebugXMLNoisy) {
+ System.out.println("Skipping unknown chunk!\n");
+ }
+ }
+ lastChunk = chunk;
+// chunk = (final ResChunk_header*)
+// (((final uint8_t*)chunk) + size1);
+ chunk = mBuffer.new ChunkHeader(chunk.myOffset() + size1);
+ }
+
+ if (mRootNode == null) {
+ ALOGW("Bad XML block: no root element node found\n");
+ mError = BAD_TYPE;
+// goto done;
+ mParser.restart();
+ return mError;
+ }
+
+ mError = mStrings.getError();
+
+ done:
+ mParser.restart();
+ return mError;
+ }
+
+ public int getError()
+ {
+ return mError;
+ }
+
+ void uninit()
+ {
+ mError = NO_INIT;
+ mStrings.uninit();
+ if (isTruthy(mOwnedData)) {
+// free(mOwnedData);
+ mOwnedData = null;
+ }
+ mParser.restart();
+ }
+
+ int validateNode(final ResXMLTree_node node)
+ {
+ final short eventCode = dtohs(node.header().type());
+
+ int err = validate_chunk(
+ node.header(), SIZEOF_RESXMLTREE_NODE /*sizeof(ResXMLTree_node)*/,
+ mDataLen, "ResXMLTree_node");
+
+ if (err >= NO_ERROR) {
+ // Only perform additional validation on START nodes
+ if (eventCode != RES_XML_START_ELEMENT_TYPE) {
+ return NO_ERROR;
+ }
+
+ final short headerSize = dtohs(node.header().headerSize());
+ final int size = dtohl(node.header().size());
+// final ResXMLTree_attrExt attrExt = (final ResXMLTree_attrExt*)
+// (((final uint8_t*)node) + headerSize);
+ ResXMLTree_attrExt attrExt = mBuffer.new XmlTreeAttrExt(node.myOffset() + headerSize);
+ // check for sensical values pulled out of the stream so far...
+ if ((size >= headerSize + SIZEOF_RESXMLTREE_ATTR_EXT /*sizeof(ResXMLTree_attrExt)*/)
+ && (attrExt.myOffset() > node.myOffset())) {
+ final int attrSize = ((int)dtohs(attrExt.attributeSize()))
+ * dtohs(attrExt.attributeCount());
+ if ((dtohs(attrExt.attributeStart())+attrSize) <= (size-headerSize)) {
+ return NO_ERROR;
+ }
+ ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
+ (int)(dtohs(attrExt.attributeStart())+attrSize),
+ (int)(size-headerSize));
+ }
+ else {
+ ALOGW("Bad XML start block: node header size 0x%x, size 0x%x\n",
+ (int)headerSize, (int)size);
+ }
+ return BAD_TYPE;
+ }
+
+ return err;
+
+// if (false) {
+// final boolean isStart = dtohs(node.header().type()) == RES_XML_START_ELEMENT_TYPE;
+//
+// final short headerSize = dtohs(node.header().headerSize());
+// final int size = dtohl(node.header().size());
+//
+// if (headerSize >= (isStart ? sizeof(ResXMLTree_attrNode) : sizeof(ResXMLTree_node))) {
+// if (size >= headerSize) {
+// if ((( final uint8_t*)node) <=(mDataEnd - size)){
+// if (!isStart) {
+// return NO_ERROR;
+// }
+// if ((((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount))
+// <= (size - headerSize)) {
+// return NO_ERROR;
+// }
+// ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
+// ((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount),
+// (int) (size - headerSize));
+// return BAD_TYPE;
+// }
+// ALOGW("Bad XML block: node at 0x%x extends beyond data end 0x%x\n",
+// (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),(int) mSize);
+// return BAD_TYPE;
+// }
+// ALOGW("Bad XML block: node at 0x%x header size 0x%x smaller than total size 0x%x\n",
+// (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),
+// (int) headerSize, (int) size);
+// return BAD_TYPE;
+// }
+// ALOGW("Bad XML block: node at 0x%x header size 0x%x too small\n",
+// (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),
+// (int) headerSize);
+// return BAD_TYPE;
+// }
+ }
+
+ public ResStringPool getStrings() {
+ return mParser.getStrings();
+ }
+
+ public int next() {
+ return mParser.next();
+ }
+
+ static class XmlBuffer {
+ private final ByteBuffer byteBuffer;
+
+ public XmlBuffer(byte[] data) {
+ this.byteBuffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ private class ChunkHeader implements ResChunk_header {
+ private final int offset;
+
+ public ChunkHeader(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public short type() {
+ return byteBuffer.getShort(offset);
+ }
+
+ @Override
+ public short headerSize() {
+ return byteBuffer.getShort(offset + 2);
+ }
+
+ @Override
+ public int size() {
+ return byteBuffer.getInt(offset + 4);
+ }
+
+ @Override
+ public int myOffset() {
+ return offset;
+ }
+ }
+
+ class XmlResStringPool {
+ private final int offset;
+ private final ResStringPool_header header;
+
+ XmlResStringPool(int offset) {
+ this.offset = offset;
+ header = new XmlResStringPoolHeader(offset);
+ }
+
+ public List<String> strings() {
+ int stringCount = header.stringCount();
+ ArrayList<String> list = new ArrayList<>(stringCount);
+ for (int i = 0; i < stringCount; i++) {
+// list.add(header.string(i));
+ }
+ return list;
+ }
+ }
+
+ private class XmlResStringPoolHeader implements ResStringPool_header {
+ private int offset;
+
+ public XmlResStringPoolHeader(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public ResChunk_header header() {
+ return new ChunkHeader(offset);
+ }
+
+ @Override
+ public int stringCount() {
+ return byteBuffer.getInt(offset + ResChunk_header.SIZEOF);
+ }
+
+ @Override
+ public int styleCount() {
+ return byteBuffer.getInt(offset + ResChunk_header.SIZEOF + 4);
+ }
+
+ @Override
+ public int flags() {
+ return byteBuffer.getInt(offset + ResChunk_header.SIZEOF + 8);
+ }
+
+ @Override
+ public int stringsStart() {
+ return byteBuffer.getInt(offset + ResChunk_header.SIZEOF + 12);
+ }
+
+ @Override
+ public int stylesStart() {
+ return byteBuffer.getInt(offset + ResChunk_header.SIZEOF + 16);
+ }
+ }
+
+ class XmlTreeHeader implements ResXMLTree_header {
+ private final int offset;
+
+ public XmlTreeHeader(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public ResChunk_header header() {
+ return new ChunkHeader(offset);
+ }
+
+ @Override
+ public int myOffset() {
+ return offset;
+ }
+ }
+
+ private class XmlTreeAttrExt implements ResXMLTree_attrExt {
+ private final int offset;
+
+ public XmlTreeAttrExt(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public ResStringPool_ref ns() {
+ return new MyResStringPoolRef(offset);
+ }
+
+ @Override
+ public ResStringPool_ref name() {
+ return new MyResStringPoolRef(offset + 4);
+ }
+
+ @Override
+ public short attributeStart() {
+ return byteBuffer.getShort(offset + 8);
+ }
+
+ @Override
+ public short attributeSize() {
+ return byteBuffer.getShort(offset + 10);
+ }
+
+ @Override
+ public short attributeCount() {
+ return byteBuffer.getShort(offset + 12);
+ }
+
+ @Override
+ public short idIndex() {
+ return byteBuffer.getShort(offset + 14);
+ }
+
+ @Override
+ public short classIndex() {
+ return byteBuffer.getShort(offset + 16);
+ }
+
+ @Override
+ public short styleIndex() {
+ return byteBuffer.getShort(offset + 18);
+ }
+
+ @Override
+ public ResXMLTree_attribute attributeAt(int idx) {
+ return new ResXmlTreeAttribute(offset + SIZEOF_RESXMLTREE_ATTR_EXT + SIZEOF_RESXMLTREE_ATTR_EXT * idx);
+ }
+
+ @Override
+ public int myOffset() {
+ return offset;
+ }
+ }
+
+ private class ResXmlTreeAttribute implements ResXMLTree_attribute {
+ private final int offset;
+
+ public ResXmlTreeAttribute(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public ResStringPool_ref ns() {
+ return new MyResStringPoolRef(offset);
+ }
+
+ @Override
+ public ResStringPool_ref name() {
+ return new MyResStringPoolRef(offset + 4);
+ }
+
+ @Override
+ public ResStringPool_ref rawValue() {
+ return new MyResStringPoolRef(offset + 8);
+ }
+
+ @Override
+ public ResValue typedValue() {
+ int dataType = byteBuffer.getInt(offset + 12);
+ int data = byteBuffer.getInt(offset + 16);
+ return new ResValue(dataType, data);
+ }
+ }
+
+ class XmlTreeNode implements ResXMLTree_node {
+ private final int offset;
+
+ public XmlTreeNode(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public ResChunk_header header() {
+ return new ChunkHeader(offset);
+ }
+
+ @Override
+ public int lineNumber() {
+ return byteBuffer.getInt(offset + 8);
+ }
+
+ @Override
+ public ResStringPool_ref comment() {
+ return new MyResStringPoolRef(offset + 12);
+ }
+
+ @Override
+ public int myOffset() {
+ return offset;
+ }
+ }
+
+ private class MyResStringPoolRef implements ResStringPool_ref {
+ private int offset;
+
+ private MyResStringPoolRef(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int index() {
+ return byteBuffer.getInt(offset);
+ }
+ }
+ }
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java b/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java
new file mode 100644
index 0000000..e8a9187
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java
@@ -0,0 +1,318 @@
+package org.robolectric.res.android;
+
+import static org.robolectric.res.android.Errors.BAD_TYPE;
+import static org.robolectric.res.android.Errors.NO_ERROR;
+import static org.robolectric.res.android.Util.ALOGW;
+import static org.robolectric.res.android.Util.dtohl;
+import static org.robolectric.res.android.Util.dtohs;
+
+public class ResourceTypes {
+ static int validate_chunk(ResChunk_header chunk,
+ int minSize,
+ int dataLen,
+ String name)
+ {
+ final short headerSize = dtohs(chunk.headerSize());
+ final int size = dtohl(chunk.size());
+
+ if (headerSize >= minSize) {
+ if (headerSize <= size) {
+ if (((headerSize|size)&0x3) == 0) {
+ if (size <= (dataLen)) {
+ return NO_ERROR;
+ }
+ ALOGW("%s data size 0x%x extends beyond resource end.",
+ name, size /*, (dataEnd-((const uint8_t*)chunk))*/);
+ return BAD_TYPE;
+ }
+ ALOGW("%s size 0x%x or headerSize 0x%x is not on an integer boundary.",
+ name, (int)size, (int)headerSize);
+ return BAD_TYPE;
+ }
+ ALOGW("%s size 0x%x is smaller than header size 0x%x.",
+ name, size, headerSize);
+ return BAD_TYPE;
+ }
+ ALOGW("%s header size 0x%04x is too small.",
+ name, headerSize);
+ return BAD_TYPE;
+ }
+
+ interface WithOffset {
+ int myOffset();
+ }
+
+ /** ********************************************************************
+ * Base Types
+ *
+ * These are standard types that are shared between multiple specific
+ * resource types.
+ *
+ *********************************************************************** */
+
+ /**
+ * Header that appears at the front of every data chunk in a resource.
+ */
+ interface ResChunk_header extends WithOffset
+ {
+ int SIZEOF = 8;
+
+ // Type identifier for this chunk. The meaning of this value depends
+ // on the containing chunk.
+ short type();
+
+ // Size of the chunk header (in bytes). Adding this value to
+ // the address of the chunk allows you to find its associated data
+ // (if any).
+ short headerSize();
+
+ // Total size of this chunk (in bytes). This is the chunkSize plus
+ // the size of any data associated with the chunk. Adding this value
+ // to the chunk allows you to completely skip its contents (including
+ // any child chunks). If this value is the same as chunkSize, there is
+ // no data associated with the chunk.
+ int size();
+ };
+
+ public static final int RES_NULL_TYPE = 0x0000;
+ public static final int RES_STRING_POOL_TYPE = 0x0001;
+ public static final int RES_TABLE_TYPE = 0x0002;
+ public static final int RES_XML_TYPE = 0x0003;
+
+ // Chunk types in RES_XML_TYPE
+ public static final int RES_XML_FIRST_CHUNK_TYPE = 0x0100;
+ public static final int RES_XML_START_NAMESPACE_TYPE= 0x0100;
+ public static final int RES_XML_END_NAMESPACE_TYPE = 0x0101;
+ public static final int RES_XML_START_ELEMENT_TYPE = 0x0102;
+ public static final int RES_XML_END_ELEMENT_TYPE = 0x0103;
+ public static final int RES_XML_CDATA_TYPE = 0x0104;
+ public static final int RES_XML_LAST_CHUNK_TYPE = 0x017f;
+ // This contains a uint32_t array mapping strings in the string
+ // pool back to resource identifiers. It is optional.
+ public static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
+
+ // Chunk types in RES_TABLE_TYPE
+ public static final int RES_TABLE_PACKAGE_TYPE = 0x0200;
+ public static final int RES_TABLE_TYPE_TYPE = 0x0201;
+ public static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
+ public static final int RES_TABLE_LIBRARY_TYPE = 0x0203;
+
+ /**
+ * Definition for a pool of strings. The data of this chunk is an
+ * array of uint32_t providing indices into the pool, relative to
+ * stringsStart. At stringsStart are all of the UTF-16 strings
+ * concatenated together; each starts with a uint16_t of the string's
+ * length and each ends with a 0x0000 terminator. If a string is >
+ * 32767 characters, the high bit of the length is set meaning to take
+ * those 15 bits as a high word and it will be followed by another
+ * uint16_t containing the low word.
+ *
+ * If styleCount is not zero, then immediately following the array of
+ * uint32_t indices into the string table is another array of indices
+ * into a style table starting at stylesStart. Each entry in the
+ * style table is an array of ResStringPool_span structures.
+ */
+ interface ResStringPool_header
+ {
+ ResChunk_header header();
+
+ // Number of strings in this pool (number of uint32_t indices that follow
+ // in the data).
+ int stringCount();
+
+ // Number of style span arrays in the pool (number of uint32_t indices
+ // follow the string indices).
+ int styleCount();
+
+ // Flags.
+// enum {
+ // If set, the string index is sorted by the string values (based
+ // on strcmp16()).
+ public static final int SORTED_FLAG = 1<<0;
+
+ // String pool is encoded in UTF-8
+ public static final int UTF8_FLAG = 1<<8;
+// };
+ int flags();
+
+ // Index from header of the string data.
+ int stringsStart();
+
+ // Index from header of the style data.
+ int stylesStart();
+ };
+
+ /**
+ * This structure defines a span of style information associated with
+ * a string in the pool.
+ */
+ interface ResStringPool_span
+ {
+// enum {
+ public static final int END = 0xFFFFFFFF;
+// };
+
+ // This is the name of the span -- that is, the name of the XML
+ // tag that defined it. The special value END (0xFFFFFFFF) indicates
+ // the end of an array of spans.
+ ResStringPool_ref name();
+
+ // The range of characters in the string that this span applies to.
+ int firstChar();
+ int lastChar();
+ };
+
+
+ /** ********************************************************************
+ * XML Tree
+ *
+ * Binary representation of an XML document. This is designed to
+ * express everything in an XML document, in a form that is much
+ * easier to parse on the device.
+ *
+ *********************************************************************** */
+
+ /**
+ * XML tree header. This appears at the front of an XML tree,
+ * describing its content. It is followed by a flat array of
+ * ResXMLTree_node structures; the hierarchy of the XML document
+ * is described by the occurrance of RES_XML_START_ELEMENT_TYPE
+ * and corresponding RES_XML_END_ELEMENT_TYPE nodes in the array.
+ */
+ interface ResXMLTree_header extends WithOffset
+ {
+ ResChunk_header header();
+ };
+
+ /**
+ * Basic XML tree node. A single item in the XML document. Extended info
+ * about the node can be found after header.headerSize.
+ */
+ interface ResXMLTree_node extends WithOffset
+ {
+ ResChunk_header header();
+
+ // Line number in original source file at which this element appeared.
+ int lineNumber();
+
+ // Optional XML comment that was associated with this element; -1 if none.
+ ResStringPool_ref comment();
+ };
+
+ /**
+ * Extended XML tree node for CDATA tags -- includes the CDATA string.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+ interface ResXMLTree_cdataExt
+ {
+ // The raw CDATA character data.
+ ResStringPool_ref data();
+
+ // The typed value of the character data if this is a CDATA node.
+ ResValue typedData();
+ };
+
+ /**
+ * Extended XML tree node for namespace start/end nodes.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+ interface ResXMLTree_namespaceExt
+ {
+ // The prefix of the namespace.
+ ResStringPool_ref prefix();
+
+ // The URI of the namespace.
+ ResStringPool_ref uri();
+ };
+
+ /**
+ * Extended XML tree node for element start/end nodes.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+ interface ResXMLTree_endElementExt
+ {
+ // String of the full namespace of this element.
+ ResStringPool_ref ns();
+
+ // String name of this node if it is an ELEMENT; the raw
+ // character data if this is a CDATA node.
+ ResStringPool_ref name();
+ };
+
+ /**
+ * Extended XML tree node for start tags -- includes attribute
+ * information.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+ interface ResXMLTree_attrExt extends WithOffset
+ {
+ // String of the full namespace of this element.
+ ResStringPool_ref ns();
+
+ // String name of this node if it is an ELEMENT; the raw
+ // character data if this is a CDATA node.
+ ResStringPool_ref name();
+
+ // Byte offset from the start of this structure where the attributes start.
+ short attributeStart();
+
+ // Size of the ResXMLTree_attribute structures that follow.
+ short attributeSize();
+
+ // Number of attributes associated with an ELEMENT. These are
+ // available as an array of ResXMLTree_attribute structures
+ // immediately following this node.
+ short attributeCount();
+
+ // Index (1-based) of the "id" attribute. 0 if none.
+ short idIndex();
+
+ // Index (1-based) of the "class" attribute. 0 if none.
+ short classIndex();
+
+ // Index (1-based) of the "style" attribute. 0 if none.
+ short styleIndex();
+
+ ResXMLTree_attribute attributeAt(int idx);
+ };
+
+ interface ResXMLTree_attribute
+ {
+ // Namespace of this attribute.
+ ResStringPool_ref ns();
+
+ // Name of this attribute.
+ ResStringPool_ref name();
+
+ // The original raw string value of this attribute.
+ ResStringPool_ref rawValue();
+
+ // Processesd typed value of this attribute.
+ ResValue typedValue();
+ };
+
+ /**
+ * This is a reference to a unique entry (a ResTable_entry structure)
+ * in a resource table. The value is structured as: 0xpptteeee,
+ * where pp is the package index, tt is the type index in that
+ * package, and eeee is the entry index in that type. The package
+ * and type values start at 1 for the first item, to help catch cases
+ * where they have not been supplied.
+ */
+ interface ResTable_ref
+ {
+ int ident();
+ };
+
+ /**
+ * Reference to a string in a string pool.
+ */
+ interface ResStringPool_ref
+ {
+ // Index into the string pool table (uint32_t-offset from the indices
+ // immediately after ResStringPool_header) at which to find the location
+ // of the string data in the pool.
+ int index();
+ };
+
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/String8.java b/resources/src/main/java/org/robolectric/res/android/String8.java
index 08a6591..f0391e1 100644
--- a/resources/src/main/java/org/robolectric/res/android/String8.java
+++ b/resources/src/main/java/org/robolectric/res/android/String8.java
@@ -20,7 +20,11 @@
this(path.string());
}
-//size_t String8::length() const
+ public String8(String value, int len) {
+ this(value.substring(0, len));
+ }
+
+ //size_t String8::length() const
//{
// return SharedBuffer::sizeFromData(mString)-1;
//}
diff --git a/resources/src/main/java/org/robolectric/res/android/Util.java b/resources/src/main/java/org/robolectric/res/android/Util.java
index 4b64a35..37a3f68 100644
--- a/resources/src/main/java/org/robolectric/res/android/Util.java
+++ b/resources/src/main/java/org/robolectric/res/android/Util.java
@@ -40,6 +40,10 @@
return o != null;
}
+ static void ALOGD(String message, Object... args) {
+ System.out.println("DEBUG: " + String.format(message, args));
+ }
+
static void ALOGW(String message, Object... args) {
System.out.println("WARN: " + String.format(message, args));
}
diff --git a/resources/src/main/java/org/robolectric/res/android/ZipArchiveHandle.java b/resources/src/main/java/org/robolectric/res/android/ZipArchiveHandle.java
new file mode 100644
index 0000000..b170db9
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ZipArchiveHandle.java
@@ -0,0 +1,11 @@
+package org.robolectric.res.android;
+
+import java.util.zip.ZipFile;
+
+public class ZipArchiveHandle {
+ final ZipFile zipFile;
+
+ public ZipArchiveHandle(ZipFile zipFile) {
+ this.zipFile = zipFile;
+ }
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java b/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java
new file mode 100644
index 0000000..389fa49
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java
@@ -0,0 +1,285 @@
+package org.robolectric.res.android;
+
+import static org.robolectric.res.android.Errors.NAME_NOT_FOUND;
+import static org.robolectric.res.android.Errors.NO_ERROR;
+import static org.robolectric.res.android.Util.ALOGW;
+import static org.robolectric.res.android.Util.isTruthy;
+
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class ZipFileRO {
+
+ final int kCompressStored = 0;
+ final int kCompressDeflated = 8;
+
+ final ZipArchiveHandle mHandle;
+ final String mFileName;
+
+ ZipFileRO(ZipArchiveHandle handle, String fileName) {
+ this.mHandle = handle;
+ this.mFileName = fileName;
+ }
+
+ static class ZipEntryRO {
+ ZipEntry entry;
+ String name;
+ Object cookie;
+
+ ZipEntryRO() {
+ }
+
+ // ~ZipEntryRO() {
+ protected void finalize() {
+// EndIteration(cookie);
+ }
+
+// private:
+// ZipEntryRO(final ZipEntryRO& other);
+// ZipEntryRO& operator=(final ZipEntryRO& other);
+ };
+
+// ~ZipFileRO() {
+ protected void finalize() {
+ CloseArchive(mHandle);
+// free(mFileName);
+ }
+
+ private static int OpenArchive(String zipFileName, Ref<ZipArchiveHandle> mHandle) {
+ try {
+ mHandle.set(new ZipArchiveHandle(new ZipFile(zipFileName)));
+ return NO_ERROR;
+ } catch (IOException e) {
+ return NAME_NOT_FOUND;
+ }
+ }
+
+ private static void CloseArchive(ZipArchiveHandle mHandle) {
+ throw new UnsupportedOperationException();
+ }
+
+ private static String ErrorCodeString(int error) {
+ return "error " + error;
+ }
+
+ private int FindEntry(ZipArchiveHandle mHandle, String name, Ref<ZipEntry> zipEntryRef) {
+ ZipEntry entry = mHandle.zipFile.getEntry(name);
+ zipEntryRef.set(entry);
+ if (entry == null) {
+ return NAME_NOT_FOUND;
+ }
+ return NO_ERROR;
+ }
+
+ /*
+ * Open the specified file read-only. We memory-map the entire thing and
+ * close the file before returning.
+ */
+/* static */
+ static ZipFileRO open(final String zipFileName)
+ {
+ Ref<ZipArchiveHandle> handle = new Ref<>(null);
+ final int error = OpenArchive(zipFileName, handle);
+ if (isTruthy(error)) {
+ ALOGW("Error opening archive %s: %s", zipFileName, ErrorCodeString(error));
+ CloseArchive(handle.get());
+ return null;
+ }
+
+ return new ZipFileRO(handle.get(), zipFileName);
+ }
+
+ org.robolectric.res.android.ZipFileRO.ZipEntryRO findEntryByName(final String entryName)
+ {
+ ZipEntryRO data = new ZipEntryRO();
+
+ data.name = String(entryName);
+
+ Ref<ZipEntry> zipEntryRef = new Ref<>(data.entry);
+ final int error = FindEntry(mHandle, data.name, zipEntryRef);
+ if (isTruthy(error)) {
+ return null;
+ }
+
+ data.entry = zipEntryRef.get();
+ return data;
+ }
+
+ /*
+ * Get the useful fields from the zip entry.
+ *
+ * Returns "false" if the offsets to the fields or the contents of the fields
+ * appear to be bogus.
+ */
+ boolean getEntryInfo(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Ref<Short> pMethod,
+ Ref<Long> pUncompLen, Ref<Long> pCompLen, Ref<Long> pOffset,
+ Ref<Long> pModWhen, Ref<Long> pCrc32)
+ {
+ final ZipEntryRO zipEntry = /*reinterpret_cast<ZipEntryRO*>*/(entry);
+ final ZipEntry ze = zipEntry.entry;
+
+ if (pMethod != null) {
+ pMethod.set((short) ze.getMethod());
+ }
+ if (pUncompLen != null) {
+ pUncompLen.set(ze.getSize()); // uncompressed_length
+ }
+ if (pCompLen != null) {
+ pCompLen.set(ze.getCompressedSize());
+ }
+ if (pOffset != null) {
+ throw new UnsupportedOperationException("Figure out offset");
+ // pOffset = ze.offset;
+ }
+ if (pModWhen != null) {
+ pModWhen.set(ze.getLastModifiedTime().toMillis());
+ }
+ if (pCrc32 != null) {
+ pCrc32.set(ze.getCrc());
+ }
+
+ return true;
+ }
+
+ boolean startIteration(Object cookie) {
+ return startIteration(cookie, null, null);
+ }
+
+ boolean startIteration(/* void** */ Object cookie, final String prefix, final String suffix)
+ {
+ throw new UnsupportedOperationException("Implememnt me");
+// ZipEntryRO* ze = new ZipEntryRO;
+// String pe(prefix ? prefix : "");
+// String se(suffix ? suffix : "");
+// int error = StartIteration(mHandle, &(ze.cookie),
+// prefix ? &pe : null,
+// suffix ? &se : null);
+// if (error) {
+// ALOGW("Could not start iteration over %s: %s", mFileName, ErrorCodeString(error));
+// delete ze;
+// return false;
+// }
+//
+// *cookie = ze;
+// return true;
+ }
+
+ org.robolectric.res.android.ZipFileRO.ZipEntryRO nextEntry(/*void* */ Object cookie)
+ {
+ throw new UnsupportedOperationException("Implememnt me");
+// ZipEntryRO ze = /*reinterpret_cast<ZipEntryRO*>*/(ZipEntryRO) cookie;
+// int error = Next(ze.cookie, &(ze.entry), &(ze.name));
+// if (error) {
+// if (error != -1) {
+// ALOGW("Error iteration over %s: %s", mFileName, ErrorCodeString(error));
+// }
+// return null;
+// }
+//
+// return &(ze.entry);
+ }
+
+ void endIteration(/*void**/ Object cookie)
+ {
+// delete reinterpret_cast<ZipEntryRO*>(cookie);
+ }
+
+ void releaseEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry)
+ {
+// delete reinterpret_cast<ZipEntryRO*>(entry);
+ }
+
+ /*
+ * Copy the entry's filename to the buffer.
+ */
+ int getEntryFileName(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, String buffer, int bufLen)
+ {
+ throw new UnsupportedOperationException("Implememnt me");
+
+// final ZipEntryRO* zipEntry = reinterpret_cast<ZipEntryRO*>(entry);
+// final uint16_t requiredSize = zipEntry.name.name_length + 1;
+//
+// if (bufLen < requiredSize) {
+// ALOGW("Buffer too short, requires %d bytes for entry name", requiredSize);
+// return requiredSize;
+// }
+//
+// memcpy(buffer, zipEntry.name.name, requiredSize - 1);
+// buffer[requiredSize - 1] = '\0';
+//
+// return 0;
+ }
+
+/*
+ * Create a new FileMap object that spans the data in "entry".
+ */
+ /*FileMap*/ ZipFileRO(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry)
+ {
+ throw new UnsupportedOperationException("Implememnt me");
+
+// final ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry);
+// final ZipEntry& ze = zipEntry.entry;
+// int fd = GetFileDescriptor(mHandle);
+// size_t actualLen = 0;
+//
+// if (ze.method == kCompressStored) {
+// actualLen = ze.uncompressed_length;
+// } else {
+// actualLen = ze.compressed_length;
+// }
+//
+// FileMap* newMap = new FileMap();
+// if (!newMap.create(mFileName, fd, ze.offset, actualLen, true)) {
+// delete newMap;
+// return null;
+// }
+//
+// return newMap;
+ }
+
+ /*
+ * Uncompress an entry, in its entirety, into the provided output buffer.
+ *
+ * This doesn't verify the data's CRC, which might be useful for
+ * uncompressed data. The caller should be able to manage it.
+ */
+ boolean uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Object buffer, int size)
+ {
+ throw new UnsupportedOperationException("Implememnt me");
+// ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry);
+// final int error = ExtractToMemory(mHandle, &(zipEntry.entry),
+// (uint8_t*) buffer, size);
+// if (error) {
+// ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error));
+// return false;
+// }
+//
+// return true;
+ }
+
+ /*
+ * Uncompress an entry, in its entirety, to an open file descriptor.
+ *
+ * This doesn't verify the data's CRC, but probably should.
+ */
+ boolean uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, int fd)
+ {
+ throw new UnsupportedOperationException("Implememnt me");
+// ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry);
+// final int error = ExtractEntryToFile(mHandle, &(zipEntry.entry), fd);
+// if (error) {
+// ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error));
+// return false;
+// }
+//
+// return true;
+ }
+
+ static String String(String string) {
+ return string;
+ }
+
+ static class FileMap {
+ }
+}
diff --git a/robolectric/src/main/java/org/robolectric/res/builder/DefaultPackageManager.java b/robolectric/src/main/java/org/robolectric/res/builder/DefaultPackageManager.java
index 639e93a..d05f349 100644
--- a/robolectric/src/main/java/org/robolectric/res/builder/DefaultPackageManager.java
+++ b/robolectric/src/main/java/org/robolectric/res/builder/DefaultPackageManager.java
@@ -751,7 +751,8 @@
TempDirectory tempDirectory = RuntimeEnvironment.getTempDirectory();
if (androidManifest != null) {
- applicationInfo.sourceDir = androidManifest.getResDirectory().getParent().toString();
+// applicationInfo.sourceDir = androidManifest.getResDirectory().getParent().toString();
+ applicationInfo.sourceDir = "build/resources/test/resources.ap_"; // todo get this from AndroidManifest
} else {
applicationInfo.sourceDir = tempDirectory.createIfNotExists(applicationInfo.packageName + "-sourceDir").toAbsolutePath().toString();
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
index 93c8c31..4d36058 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
@@ -461,7 +461,7 @@
assertThat(Resources.getSystem().getIdentifier("copy", "string", TestUtil.TEST_PACKAGE)).isEqualTo(0);
}
- @Test
+ @Test @Config(sdk = 25) // todo: unpin sdk
public void testGetXml() throws Exception {
XmlResourceParser parser = resources.getXml(R.xml.preferences);
assertThat(parser).isNotNull();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
index f41eb3d..d7de705 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
@@ -3,13 +3,13 @@
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.res.android.Errors.BAD_INDEX;
+import static org.robolectric.res.android.Errors.NO_ERROR;
import static org.robolectric.shadow.api.Shadow.directlyOn;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Build.VERSION_CODES;
import android.os.ParcelFileDescriptor;
@@ -26,11 +26,13 @@
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
-import org.robolectric.res.android.BagAttributeFinder;
import org.robolectric.res.android.Asset;
import org.robolectric.res.android.Asset.AccessMode;
+import org.robolectric.res.android.BagAttributeFinder;
import org.robolectric.res.android.CppAssetManager;
import org.robolectric.res.android.DataType;
+import org.robolectric.res.android.DynamicRefTable;
+import org.robolectric.res.android.FileAsset;
import org.robolectric.res.android.Ref;
import org.robolectric.res.android.ResStringPool;
import org.robolectric.res.android.ResTable;
@@ -39,6 +41,7 @@
import org.robolectric.res.android.ResTableTheme;
import org.robolectric.res.android.ResValue;
import org.robolectric.res.android.ResXMLParser;
+import org.robolectric.res.android.ResXMLTree;
import org.robolectric.res.android.String8;
import org.robolectric.res.android.XmlAttributeFinder;
import org.robolectric.shadow.api.Shadow;
@@ -231,13 +234,6 @@
ClassParameter.from(String.class, fileName));
}
- public XmlResourceParser loadXmlResourceParser(int resId, String type)
- throws Resources.NotFoundException {
- return directlyOn(realObject, AssetManager.class, "loadXmlResourceParser",
- ClassParameter.from(int.class, resId),
- ClassParameter.from(String.class, type));
- }
-
@HiddenApi
@Implementation
public int addAssetPath(String path) {
@@ -436,8 +432,8 @@
return -1;
}
AccessMode mode = AccessMode.values()[accessMode];
- if (mode != Asset.AccessMode.ACCESS_UNKNOWN && mode != Asset.AccessMode.ACCESS_RANDOM
- && mode != Asset.AccessMode.ACCESS_STREAMING && mode != Asset.AccessMode.ACCESS_BUFFER) {
+ if (mode != FileAsset.AccessMode.ACCESS_UNKNOWN && mode != FileAsset.AccessMode.ACCESS_RANDOM
+ && mode != FileAsset.AccessMode.ACCESS_STREAMING && mode != FileAsset.AccessMode.ACCESS_BUFFER) {
throw new IllegalArgumentException("Bad access mode");
}
Asset a = isTruthy(cookie)
@@ -450,6 +446,8 @@
synchronized (assets) {
assetId = nextAssetId++;
assets.put(assetId, a);
+ // todo: something better than this
+ a.onClose = () -> destroyAsset(assetId);
}
//printf("Created Asset Stream: %p\n", a);
return assetId;
@@ -749,7 +747,7 @@
Ref<Integer> styleBagTypeSetFlags = new Ref(0);
if (xmlParser != null) {
int idx = xmlParser.indexOfStyle();
- if (idx >= 0 && xmlParser.getAttributeValue(idx, value) >= 0) {
+ if (idx >= 0 && xmlParser.getAttributeValue(idx, value.get()) >= 0) {
if (value.get().dataType == DataType.ATTRIBUTE.code()) {
if (theme.getAttribute(value.get().data, value, styleBagTypeSetFlags) < 0) {
value.get().dataType = DataType.NULL.code();
@@ -818,7 +816,7 @@
if (xmlAttrIdx != xmlAttrEnd) {
// We found the attribute we were looking for.
block = kXmlBlock;
- xmlParser.getAttributeValue(xmlAttrIdx, value);
+ xmlParser.getAttributeValue(xmlAttrIdx, value.get());
if (kDebugStyles) {
ALOGI(". From XML: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
}
@@ -937,7 +935,7 @@
@Implementation
@HiddenApi
- public static final boolean resolveAttrs(long theme,
+ public static boolean resolveAttrs(long theme,
int defStyleAttr, int defStyleRes, int[] inValues,
int[] inAttrs, int[] outValues, int[] outIndices) {
throw new UnsupportedOperationException("not yet implemented");
@@ -1183,8 +1181,46 @@
// /*package*/@HiddenApi @Implementation public static final @NativeConfig
// int getThemeChangingConfigurations(long theme);
- @HiddenApi @Implementation public final long openXmlAssetNative(int cookie, String fileName){
- throw new UnsupportedOperationException("not yet implemented");
+ @HiddenApi @Implementation public final long openXmlAssetNative(int cookie, String fileName)
+ throws FileNotFoundException {
+ CppAssetManager am = assetManagerForJavaObject();
+ if (am == null) {
+ return 0;
+ }
+
+ ALOGV("openXmlAsset in %s (Java object %s)\n", am, ShadowArscAssetManager.class);
+
+ String fileName8 = fileName;
+ if (fileName8 == null) {
+ return 0;
+ }
+
+ int assetCookie = cookie;
+ Asset a;
+ if (isTruthy(assetCookie)) {
+ a = am.openNonAsset(assetCookie, fileName8, AccessMode.ACCESS_BUFFER);
+ } else {
+ Ref<Integer> assetCookieRef = new Ref<>(assetCookie);
+ a = am.openNonAsset(fileName8, AccessMode.ACCESS_BUFFER, assetCookieRef);
+ assetCookie = assetCookieRef.get();
+ }
+
+ if (a == null) {
+ throw new FileNotFoundException(fileName8);
+ }
+
+ final DynamicRefTable dynamicRefTable =
+ am.getResources().getDynamicRefTableForCookie(assetCookie);
+ ResXMLTree block = new ResXMLTree(dynamicRefTable);
+ int err = block.setTo(a.getBuffer(true), a.getLength(), true);
+ a.close();
+// delete a;
+
+ if (err != NO_ERROR) {
+ throw new FileNotFoundException("Corrupt XML binary file");
+ }
+
+ return ShadowXmlBlock.RES_XML_TREES.getNativeObjectId(block);
}
@HiddenApi @Implementation public final String[] getArrayStringResource(int arrayResId){
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
index 07dbd80..e6ba84b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
@@ -208,16 +208,16 @@
return displayMetrics;
}
- @HiddenApi @Implementation
- public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
- ShadowAssetManager shadowAssetManager = legacyShadowOf(realResources.getAssets());
- return shadowAssetManager.loadXmlResourceParser(resId, type);
- }
-
- @HiddenApi @Implementation
- public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException {
- return loadXmlResourceParser(id, type);
- }
+// @HiddenApi @Implementation
+// public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
+// ShadowAssetManager shadowAssetManager = legacyShadowOf(realResources.getAssets());
+// return shadowAssetManager.loadXmlResourceParser(resId, type);
+// }
+//
+// @HiddenApi @Implementation
+// public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException {
+// return loadXmlResourceParser(id, type);
+// }
@Implements(value = Resources.Theme.class)
public static class ShadowTheme {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java
index 8f9c638..5df6d3e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java
@@ -188,17 +188,17 @@
return displayMetrics;
}
- @HiddenApi
- @Implementation
- public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
- ShadowAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets());
- return shadowAssetManager.loadXmlResourceParser(resId, type);
- }
-
- @HiddenApi @Implementation
- public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException {
- return loadXmlResourceParser(id, type);
- }
+// @HiddenApi
+// @Implementation
+// public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
+// ShadowAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets());
+// return shadowAssetManager.loadXmlResourceParser(resId, type);
+// }
+//
+// @HiddenApi @Implementation
+// public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException {
+// return loadXmlResourceParser(id, type);
+// }
@Implements(value = ResourcesImpl.ThemeImpl.class, minSdk = N, isInAndroidSdk = false)
public static class ShadowThemeImpl {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStringBlock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStringBlock.java
index e00754e..0dcfad4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStringBlock.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStringBlock.java
@@ -3,11 +3,10 @@
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
-import com.google.common.base.Preconditions;
-import com.sun.org.glassfish.external.statistics.annotations.Reset;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
+import org.robolectric.annotation.Resetter;
import org.robolectric.res.android.ResStringPool;
@Implements(className = "android.content.res.StringBlock", isInAndroidSdk = false)
@@ -16,7 +15,7 @@
@RealObject
Object realObject;
- private static NativeObjRegistry<ResStringPool> nativeStringPoolRegistry = new NativeObjRegistry();
+ private static NativeObjRegistry<ResStringPool> nativeStringPoolRegistry = new NativeObjRegistry<>();
static long getNativePointer(ResStringPool tableStringBlock) {
return nativeStringPoolRegistry.getNativeObjectId(tableStringBlock);
@@ -58,8 +57,8 @@
return null;
}
- @Reset
- public void reset() {
+ @Resetter
+ public static void reset() {
nativeStringPoolRegistry.clear();
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowXmlBlock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowXmlBlock.java
new file mode 100644
index 0000000..5d28aa7
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowXmlBlock.java
@@ -0,0 +1,202 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.res.android.Errors.NO_ERROR;
+
+import android.os.Build.VERSION_CODES;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.res.android.ResStringPool;
+import org.robolectric.res.android.ResXMLParser;
+import org.robolectric.res.android.ResXMLTree;
+import org.xmlpull.v1.XmlPullParserException;
+
+@Implements(className = "android.content.res.XmlBlock", isInAndroidSdk = false)
+public class ShadowXmlBlock {
+ static final NativeObjRegistry<ResXMLTree> RES_XML_TREES = new NativeObjRegistry<>();
+ private static final NativeObjRegistry<ResXMLParser> RES_XML_PARSERS = new NativeObjRegistry<>();
+ private static final NativeObjRegistry<ResStringPool> RES_STRING_POOLS = new NativeObjRegistry<>();
+
+ @Implementation
+ public static long nativeCreate(byte[] bArray, int off, int len) {
+ if (bArray == null) {
+ throw new NullPointerException();
+ }
+
+ int bLen = bArray.length;
+ if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ // todo: optimize
+ byte[] b = new byte[len];
+ System.arraycopy(bArray, off, b, 0, len);
+
+ ResXMLTree osb = new ResXMLTree(null);
+ osb.setTo(b, len, true);
+// env->ReleaseByteArrayElements(bArray, b, 0);
+
+ if (osb.getError() != NO_ERROR) {
+ throw new IllegalArgumentException();
+ }
+
+ return RES_XML_TREES.getNativeObjectId(osb);
+ }
+
+ // todo: implement pre-Lollipop
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static long nativeGetStringBlock(long obj) {
+ ResXMLTree osb = RES_XML_TREES.getNativeObject(obj);
+// if (osb == NULL) {
+// jniThrowNullPointerException(env, NULL);
+// return 0;
+// }
+
+ return RES_STRING_POOLS.getNativeObjectId(osb.getStrings());
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static long nativeCreateParseState(long obj) {
+ ResXMLTree osb = RES_XML_TREES.getNativeObject(obj);
+// if (osb == NULL) {
+// jniThrowNullPointerException(env, NULL);
+// return 0;
+// }
+
+// ResXMLParser st = new ResXMLParser(osb);
+ ResXMLParser st = osb.mParser;
+// if (st == NULL) {
+// jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+// return 0;
+// }
+
+ st.restart();
+
+ return RES_XML_PARSERS.getNativeObjectId(st);
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeNext(long state) throws XmlPullParserException {
+ ResXMLTree st = RES_XML_TREES.getNativeObject(state);
+ if (st == null) {
+ return ResXMLParser.event_code_t.END_DOCUMENT;
+ }
+
+ do {
+ int code = st.next();
+ switch (code) {
+ case ResXMLParser.event_code_t.START_TAG:
+ return 2;
+ case ResXMLParser.event_code_t.END_TAG:
+ return 3;
+ case ResXMLParser.event_code_t.TEXT:
+ return 4;
+ case ResXMLParser.event_code_t.START_DOCUMENT:
+ return 0;
+ case ResXMLParser.event_code_t.END_DOCUMENT:
+ return 1;
+ case ResXMLParser.event_code_t.BAD_DOCUMENT:
+// goto bad;
+ throw new XmlPullParserException("Corrupt XML binary file");
+ default:
+ break;
+ }
+
+ } while (true);
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetNamespace(long state) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetName(long state) {
+ ResXMLParser resXMLParser = getResXMLParser(state);
+ return resXMLParser.getElementNameID();
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetText(long state) {
+ ResXMLParser resXMLParser = getResXMLParser(state);
+ return resXMLParser.getTextID();
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetLineNumber(long state) {
+ ResXMLParser resXMLParser = getResXMLParser(state);
+ return resXMLParser.getLineNumber();
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetAttributeCount(long state) {
+ ResXMLParser resXMLParser = getResXMLParser(state);
+ return resXMLParser.getAttributeCount();
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetAttributeNamespace(long state, int idx) {
+ ResXMLParser resXMLParser = getResXMLParser(state);
+ return resXMLParser.getAttributeNamespaceID(idx);
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetAttributeName(long state, int idx) {
+ ResXMLParser resXMLParser = getResXMLParser(state);
+ return resXMLParser.getAttributeNameID(idx);
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetAttributeResource(long state, int idx) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetAttributeDataType(long state, int idx) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetAttributeData(long state, int idx) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetAttributeStringValue(long state, int idx) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetIdAttribute(long state) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetClassAttribute(long state) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetStyleAttribute(long state) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static int nativeGetAttributeIndex(long state, String namespace, String name) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static void nativeDestroyParseState(long state) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ public static void nativeDestroy(long obj) {
+ throw new UnsupportedOperationException("implement me");
+ }
+
+ private static ResXMLParser getResXMLParser(long state) {
+ return RES_XML_PARSERS.getNativeObject(state);
+ }
+}