blob: e9b2d7d66c8c084dcee6110adbee3569b748478a [file] [log] [blame]
package org.robolectric.res.android;
import static org.robolectric.res.android.Util.ALOGI;
import java.util.LinkedList;
import java.util.List;
/**
* 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:
// virtual ~Asset(void) = default;
// static int32_t getGlobalCount();
// static String8 getAssetAllocations();
//
// /* used when opening an asset */
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;
//#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 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()
//{
// AutoMutex _l(gAssetLock);
// String8 res;
// Asset* cur = gHead;
// while (cur != NULL) {
// if (cur->isAllocated()) {
// res.append(" ");
// res.append(cur->getAssetSource());
// off64_t size = (cur->getLength()+512)/1024;
// char buf[64];
// sprintf(buf, ": %dK\n", (int)size);
// res.append(buf);
// }
// cur = cur->mNext;
// }
// return res;
//}
//Asset::Asset(void)
// : mAccessMode(ACCESS_UNKNOWN), mNext(NULL), mPrev(NULL)
//{
//}
/*
* Create a new Asset from a file on disk. There is a fair chance that
* the file doesn't actually exist.
*
* We can use "mode" to decide how we want to go about it.
*/
static Asset createFromFile(String fileName, AccessMode mode)
{
// _FileAsset* pAsset;
// status_t result;
// off64_t length;
// int fd;
// fd = open(fileName, O_RDONLY | O_BINARY);
// if (fd < 0)
// return NULL;
// /*
// * Under Linux, the lseek fails if we actually opened a directory. To
// * be correct we should test the file type explicitly, but since we
// * always open things read-only it doesn't really matter, so there's
// * no value in incurring the extra overhead of an fstat() call.
// */
// // TODO(kroot): replace this with fstat despite the plea above.
//#if 1
// length = lseek64(fd, 0, SEEK_END);
// if (length < 0) {
// ::close(fd);
// return NULL;
// }
// (void) lseek64(fd, 0, SEEK_SET);
//#else
// struct stat st;
// if (fstat(fd, &st) < 0) {
// ::close(fd);
// return NULL;
// }
// if (!S_ISREG(st.st_mode)) {
// ::close(fd);
// return NULL;
// }
//#endif
// pAsset = new _FileAsset;
// result = pAsset->openChunk(fileName, fd, 0, length);
// if (result != NO_ERROR) {
// delete pAsset;
// return NULL;
// }
// pAsset->mAccessMode = mode;
// return pAsset;
return null;
}
/*
* 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.
// */
//off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn)
//{
// off64_t 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() {
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()
{
// 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;
//
}
}