
org.robolectric.res.android.Asset Maven / Gradle / Ivy
package org.robolectric.res.android;
import static org.robolectric.res.android.Asset.AccessMode.ACCESS_BUFFER;
import static org.robolectric.res.android.Errors.NO_ERROR;
import static org.robolectric.res.android.Util.ALOGE;
import static org.robolectric.res.android.Util.ALOGV;
import static org.robolectric.res.android.Util.ALOGW;
import static org.robolectric.res.android.Util.isTruthy;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.robolectric.res.FileTypedResource;
import org.robolectric.res.Fs;
// transliterated from
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/Asset.cpp
// and
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/Asset.h
/*
* 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.
*/
@SuppressWarnings("NewApi")
public abstract class Asset {
public static final Asset EXCLUDED_ASSET = new _FileAsset();
public Runnable onClose;
public static Asset newFileAsset(FileTypedResource fileTypedResource) throws IOException {
_FileAsset fileAsset = new _FileAsset();
Path path = fileTypedResource.getPath();
fileAsset.mFileName = Fs.externalize(path);
fileAsset.mLength = Files.size(path);
fileAsset.mBuf = Fs.getBytes(path);
return fileAsset;
}
// public:
// virtual ~Asset(void) = default;
// static int getGlobalCount();
// static String8 getAssetAllocations();
public enum AccessMode {
ACCESS_UNKNOWN(0),
/* read chunks, and seek forward and backward */
ACCESS_RANDOM(1),
/* read sequentially, with an occasional forward seek */
ACCESS_STREAMING(2),
/* caller plans to ask for a read-only buffer with all data */
ACCESS_BUFFER(3);
private final int mode;
AccessMode(int mode) {
this.mode = mode;
}
public int mode() {
return mode;
}
public static AccessMode fromInt(int mode) {
for (AccessMode enumMode : values()) {
if (mode == enumMode.mode()) {
return enumMode;
}
}
throw new IllegalArgumentException("invalid mode " + Integer.toString(mode));
}
}
public static final int SEEK_SET = 0;
public static final int SEEK_CUR = 1;
public static final int SEEK_END = 2;
public final int read(byte[] buf, int count) {
return read(buf, 0, count);
}
/*
* Read data from the current offset. Returns the actual number of
* bytes read, 0 on EOF, or -1 on error.
*
* Transliteration note: added bufOffset to translate to: index into buf to start writing at
*/
public abstract int read(byte[] buf, int bufOffset, int count);
/*
* Seek to the specified offset. "whence" uses the same values as
* lseek/fseek. Returns the new position on success, or (long) -1
* on failure.
*/
public abstract long seek(long offset, int whence);
/*
* Close the asset, freeing all associated resources.
*/
public abstract void close();
/*
* Get a pointer to a buffer with the entire contents of the file.
*/
public abstract byte[] getBuffer(boolean wordAligned);
/*
* Get the total amount of data that can be read.
*/
public abstract long getLength();
/*
* Get the total amount of data that can be read from the current position.
*/
public abstract long getRemainingLength();
/*
* Open a new file descriptor that can be used to read this asset.
* Returns -1 if you can not use the file descriptor (for example if the
* asset is compressed).
*/
public abstract FileDescriptor openFileDescriptor(Ref outStart, Ref outLength);
public abstract File getFile();
public abstract String getFileName();
/*
* Return whether this asset's buffer is allocated in RAM (not mmapped).
* Note: not virtual so it is safe to call even when being destroyed.
*/
abstract boolean isAllocated(); // { return false; }
/*
* Get a string identifying the asset's source. This might be a full
* path, it might be a colon-separated list of identifiers.
*
* This is NOT intended to be used for anything except debug output.
* DO NOT try to parse this or use it to open a file.
*/
final String getAssetSource() {
return mAssetSource.string();
}
public abstract boolean isNinePatch();
// protected:
// /*
// * Adds this Asset to the global Asset list for debugging and
// * accounting.
// * Concrete subclasses must call this in their finalructor.
// */
// static void registerAsset(Asset asset);
//
// /*
// * Removes this Asset from the global Asset list.
// * Concrete subclasses must call this in their destructor.
// */
// static void unregisterAsset(Asset asset);
//
// Asset(void); // finalructor; only invoked indirectly
//
// /* handle common seek() housekeeping */
// long handleSeek(long offset, int whence, long curPosn, long maxPosn);
/* set the asset source string */
void setAssetSource(final String8 path) {
mAssetSource = path;
}
AccessMode getAccessMode() {
return mAccessMode;
}
// private:
// /* these operations are not implemented */
// Asset(final Asset& src);
// Asset& operator=(final Asset& src);
//
// /* AssetManager needs access to our "create" functions */
// friend class AssetManager;
//
// /*
// * Create the asset from a named file on disk.
// */
// static Asset createFromFile(final String fileName, AccessMode mode);
//
// /*
// * Create the asset from a named, compressed file on disk (e.g. ".gz").
// */
// static Asset createFromCompressedFile(final String fileName,
// AccessMode mode);
//
// #if 0
// /*
// * Create the asset from a segment of an open file. This will fail
// * if "offset" and "length" don't fit within the bounds of the file.
// *
// * The asset takes ownership of the file descriptor.
// */
// static Asset createFromFileSegment(int fd, long offset, int length,
// AccessMode mode);
//
// /*
// * Create from compressed data. "fd" should be seeked to the start of
// * the compressed data. This could be inside a gzip file or part of a
// * Zip archive.
// *
// * The asset takes ownership of the file descriptor.
// *
// * This may not verify the validity of the compressed data until first
// * use.
// */
// static Asset createFromCompressedData(int fd, long offset,
// int compressionMethod, int compressedLength,
// int uncompressedLength, AccessMode mode);
// #endif
//
// /*
// * Create the asset from a memory-mapped file segment.
// *
// * The asset takes ownership of the FileMap.
// */
// static Asset createFromUncompressedMap(FileMap dataMap, AccessMode mode);
//
// /*
// * Create the asset from a memory-mapped file segment with compressed
// * data.
// *
// * The asset takes ownership of the FileMap.
// */
// static Asset createFromCompressedMap(FileMap dataMap,
// int uncompressedLen, AccessMode mode);
//
//
// /*
// * Create from a reference-counted chunk of shared memory.
// */
// // TODO
AccessMode mAccessMode; // how the asset was opened
String8 mAssetSource; // debug string
Asset mNext; // linked list.
Asset mPrev;
static final boolean kIsDebug = false;
static final Object gAssetLock = new Object();
static int gCount = 0;
static Asset gHead = null;
static Asset gTail = null;
void registerAsset(Asset asset) {
// AutoMutex _l(gAssetLock);
// gCount++;
// asset.mNext = asset.mPrev = null;
// if (gTail == null) {
// gHead = gTail = asset;
// } else {
// asset.mPrev = gTail;
// gTail.mNext = asset;
// gTail = asset;
// }
//
// if (kIsDebug) {
// ALOGI("Creating Asset %s #%d\n", asset, gCount);
// }
}
void unregisterAsset(Asset asset) {
// AutoMutex _l(gAssetLock);
// gCount--;
// if (gHead == asset) {
// gHead = asset.mNext;
// }
// if (gTail == asset) {
// gTail = asset.mPrev;
// }
// if (asset.mNext != null) {
// asset.mNext.mPrev = asset.mPrev;
// }
// if (asset.mPrev != null) {
// asset.mPrev.mNext = asset.mNext;
// }
// asset.mNext = asset.mPrev = null;
//
// if (kIsDebug) {
// ALOGI("Destroying Asset in %s #%d\n", asset, gCount);
// }
}
public static int getGlobalCount() {
// AutoMutex _l(gAssetLock);
synchronized (gAssetLock) {
return gCount;
}
}
public static String getAssetAllocations() {
// AutoMutex _l(gAssetLock);
synchronized (gAssetLock) {
StringBuilder res = new StringBuilder();
Asset cur = gHead;
while (cur != null) {
if (cur.isAllocated()) {
res.append(" ");
res.append(cur.getAssetSource());
long size = (cur.getLength() + 512) / 1024;
String buf = String.format(": %dK\n", (int) size);
res.append(buf);
}
cur = cur.mNext;
}
return res.toString();
}
}
Asset() {
// : mAccessMode(ACCESS_UNKNOWN), mNext(null), mPrev(null)
mAccessMode = AccessMode.ACCESS_UNKNOWN;
}
/*
* 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(final String fileName, AccessMode mode) {
File file = new File(fileName);
if (!file.exists()) {
return null;
}
throw new UnsupportedOperationException();
// _FileAsset pAsset;
// int result;
// long 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;
}
/*
* 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.
*/
@SuppressWarnings("DoNotCallSuggester")
static Asset createFromCompressedFile(final String fileName, AccessMode mode) {
throw new UnsupportedOperationException();
// _CompressedAsset pAsset;
// int result;
// long fileLen;
// boolean 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 createFromFileSegment(int fd, long offset,
// int length, AccessMode mode)
// {
// _FileAsset pAsset;
// int 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 createFromCompressedData(int fd, long offset,
// int compressionMethod, int uncompressedLen, int compressedLen,
// AccessMode mode)
// {
// _CompressedAsset pAsset;
// int 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 createFromUncompressedMap(FileMap dataMap, AccessMode mode) {
_FileAsset pAsset;
int 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 createFromCompressedMap(FileMap dataMap, int uncompressedLen, AccessMode mode) {
_CompressedAsset pAsset;
int 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 long size mismatch
assert false;
return -1;
}
if (newOffset < 0 || newOffset > maxPosn) {
ALOGW("seek out of range: want %d, end=%d\n", newOffset, maxPosn);
return -1;
}
return newOffset;
}
/*
* An asset based on an uncompressed file on disk. It may encompass the
* entire file or just a piece of it. Access is through fread/fseek.
*/
static class _FileAsset extends Asset {
// public:
// _FileAsset(void);
// virtual ~_FileAsset(void);
//
// /*
// * Use a piece of an already-open file.
// *
// * On success, the object takes ownership of "fd".
// */
// int openChunk(final String fileName, int fd, long offset, int length);
//
// /*
// * Use a memory-mapped region.
// *
// * On success, the object takes ownership of "dataMap".
// */
// int openChunk(FileMap dataMap);
//
// /*
// * Standard Asset interfaces.
// */
// virtual ssize_t read(void* buf, int count);
// virtual long seek(long offset, int whence);
// virtual void close(void);
// virtual final void* getBuffer(boolean wordAligned);
@Override
public long getLength() {
return mLength;
}
@Override
public long getRemainingLength() {
return mLength - mOffset;
}
// virtual int openFileDescriptor(long* outStart, long* outLength) final;
@Override
boolean isAllocated() {
return mBuf != null;
}
@Override
public boolean isNinePatch() {
String fileName = getFileName();
if (mMap != null) {
fileName = mMap.getZipEntry().getName();
}
return fileName != null && fileName.toLowerCase().endsWith(".9.png");
}
//
// private:
long mStart; // absolute file offset of start of chunk
long mLength; // length of the chunk
long mOffset; // current local offset, 0 == mStart
// FILE* mFp; // for read/seek
RandomAccessFile mFp; // for read/seek
String mFileName; // for opening
/*
* To support getBuffer() we either need to read the entire thing into
* a buffer or memory-map it. For small files it's probably best to
* just read them in.
*/
// enum {
public static int kReadVsMapThreshold = 4096;
// };
FileMap mMap; // for memory map
byte[] mBuf; // for read
// final void* ensureAlignment(FileMap map);
/*
* ===========================================================================
* _FileAsset
* ===========================================================================
*/
/*
* Constructor.
*/
_FileAsset()
// : 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.
registerAsset(this);
}
/*
* Destructor. Release resources.
*/
@Override
protected 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.
unregisterAsset(this);
}
/*
* Operate on a chunk of an uncompressed file.
*
* Zero-length chunks are allowed.
*/
int openChunk(final String fileName, int fd, long offset, int length) {
throw new UnsupportedOperationException();
// assert(mFp == null); // no reopen
// assert(mMap == null);
// assert(fd >= 0);
// assert(offset >= 0);
//
// /*
// * Seek to end to get file length.
// */
// long fileLength;
// fileLength = lseek64(fd, 0, SEEK_END);
// if (fileLength == (long) -1) {
// // probably a bad file descriptor
// ALOGD("failed lseek (errno=%d)\n", errno);
// return UNKNOWN_ERROR;
// }
//
// if ((long) (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.
*/
int 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);
mBuf = dataMap.getDataPtr();
return NO_ERROR;
}
/*
* Read a chunk of data.
*/
@Override
public int read(byte[] buf, int bufOffset, int count) {
int maxLen;
int 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 = toIntExact(mLength - mOffset);
if (count > maxLen) count = maxLen;
if (!isTruthy(count)) {
return 0;
}
if (mMap != null) {
/* copy from mapped area */
// printf("map read\n");
// memcpy(buf, (String)mMap.getDataPtr() + mOffset, count);
System.arraycopy(mMap.getDataPtr(), toIntExact(mOffset), buf, bufOffset, count);
actual = count;
} else if (mBuf != null) {
/* copy from buffer */
// printf("buf read\n");
// memcpy(buf, (String)mBuf + mOffset, count);
System.arraycopy(mBuf, toIntExact(mOffset), buf, bufOffset, count);
actual = count;
} else {
/* read from the file */
// printf("file read\n");
// if (ftell(mFp) != mStart + mOffset) {
try {
if (mFp.getFilePointer() != mStart + mOffset) {
ALOGE("Hosed: %d != %d+%d\n", mFp.getFilePointer(), (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 = mFp.read(buf, 0, count);
if (actual == 0) // something failed -- I/O error?
return -1;
assert (actual == count);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
mOffset += actual;
return actual;
}
/*
* Seek to a new position.
*/
@Override
public long seek(long offset, int whence) {
long newPosn;
long actualOffset;
// compute new position within chunk
newPosn = handleSeek(offset, whence, mOffset, mLength);
if (newPosn == (long) -1) return newPosn;
actualOffset = mStart + newPosn;
if (mFp != null) {
throw new UnsupportedOperationException();
// if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
// return (long) -1;
}
mOffset = actualOffset - mStart;
return mOffset;
}
/*
* Close the asset.
*/
@Override
public void close() {
throw new UnsupportedOperationException();
// 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.
*/
@Override
public final byte[] getBuffer(boolean 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 (true /*mLength < kReadVsMapThreshold*/) {
byte[] buf;
int allocLen;
/* zero-length files are allowed; not sure about zero-len allocs */
/* (works fine with gcc + x86linux) */
allocLen = toIntExact(mLength);
if (mLength == 0) allocLen = 1;
buf = new byte[allocLen];
if (buf == null) {
ALOGE("alloc of %d bytes failed\n", (long) allocLen);
return null;
}
ALOGV("Asset %s allocating buffer size %d (smaller than threshold)", this, (int) allocLen);
if (mLength > 0) {
try {
// long oldPosn = ftell(mFp);
long oldPosn = mFp.getFilePointer();
// fseek(mFp, mStart, SEEK_SET);
mFp.seek(mStart);
// if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
if (mFp.read(buf, 0, toIntExact(mLength)) != (int) mLength) {
ALOGE("failed reading %d bytes\n", (long) mLength);
// delete[] buf;
return null;
}
// fseek(mFp, oldPosn, SEEK_SET);
mFp.seek(oldPosn);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
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)) {
if (!map.create(null, -1, mStart, toIntExact(mLength), true)) {
// delete map;
return null;
}
ALOGV(" getBuffer: mapped\n");
mMap = map;
// if (!wordAligned) {
// return mMap.getDataPtr();
// }
return ensureAlignment(mMap);
}
}
/**
* Return the file on disk representing this asset.
*
* Non-Android framework method. Based on {@link #openFileDescriptor(Ref, Ref)}.
*/
@Override
public File getFile() {
if (mMap != null) {
String fname = mMap.getFileName();
if (fname == null) {
fname = mFileName;
}
if (fname == null) {
return null;
}
// return open(fname, O_RDONLY | O_BINARY);
return new File(fname);
}
if (mFileName == null) {
return null;
}
return new File(mFileName);
}
@Override
public String getFileName() {
File file = getFile();
return file == null ? null : file.getName();
}
@Override
public FileDescriptor openFileDescriptor(Ref outStart, Ref outLength) {
if (mMap != null) {
String fname = mMap.getFileName();
if (fname == null) {
fname = mFileName;
}
if (fname == null) {
return null;
}
outStart.set(mMap.getDataOffset());
outLength.set((long) mMap.getDataLength());
// return open(fname, O_RDONLY | O_BINARY);
return open(fname);
}
if (mFileName == null) {
return null;
}
outStart.set(mStart);
outLength.set(mLength);
// return open(mFileName, O_RDONLY | O_BINARY);
return open(mFileName);
}
private static FileDescriptor open(String fname) {
try {
return new FileInputStream(new File(fname)).getFD();
} catch (IOException e) {
return null;
}
}
@SuppressWarnings("DoNotCallSuggester")
final byte[] ensureAlignment(FileMap map) {
throw new UnsupportedOperationException();
// void* data = map.getDataPtr();
// if ((((int)data)&0x3) == 0) {
// // We can return this directly if it is aligned on a word
// // boundary.
// ALOGV("Returning aligned FileAsset %s (%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 %s (%s) to buffer size %d to make it aligned.", this,
// getAssetSource(), (int)mLength);
// unsigned String 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;
// }
}
@Override
public String toString() {
if (mFileName == null) {
return "_FileAsset{" + "mMap=" + mMap + '}';
} else {
return "_FileAsset{" + "mFileName='" + mFileName + '\'' + '}';
}
}
}
/*
* An asset based on compressed data in a file.
*/
static class _CompressedAsset extends Asset {
// public:
// _CompressedAsset(void);
// virtual ~_CompressedAsset(void);
//
// /*
// * Use a piece of an already-open file.
// *
// * On success, the object takes ownership of "fd".
// */
// int openChunk(int fd, long offset, int compressionMethod,
// int uncompressedLen, int compressedLen);
//
// /*
// * Use a memory-mapped region.
// *
// * On success, the object takes ownership of "fd".
// */
// int openChunk(FileMap dataMap, int uncompressedLen);
//
// /*
// * Standard Asset interfaces.
// */
// virtual ssize_t read(void* buf, int count);
// virtual long seek(long offset, int whence);
// virtual void close(void);
// virtual final void* getBuffer(boolean wordAligned);
@Override
public long getLength() {
return mUncompressedLen;
}
@Override
public long getRemainingLength() {
return mUncompressedLen - mOffset;
}
@Override
public File getFile() {
return null;
}
@Override
public String getFileName() {
ZipEntry zipEntry = mMap.getZipEntry();
return zipEntry == null ? null : zipEntry.getName();
}
@Override
public FileDescriptor openFileDescriptor(Ref outStart, Ref outLength) {
return null;
}
@Override
boolean isAllocated() {
return mBuf != null;
}
@Override
public boolean isNinePatch() {
String fileName = getFileName();
return fileName != null && fileName.toLowerCase().endsWith(".9.png");
}
// private:
long mStart; // offset to start of compressed data
long mCompressedLen; // length of the compressed data
long mUncompressedLen; // length of the uncompressed data
long mOffset; // current offset, 0 == start of uncomp data
FileMap mMap; // for memory-mapped input
int mFd; // for file input
// class StreamingZipInflater mZipInflater; // for streaming large compressed assets
byte[] mBuf; // for getBuffer()
/*
* ===========================================================================
* _CompressedAsset
* ===========================================================================
*/
/*
* Constructor.
*/
_CompressedAsset()
// : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
// mMap(null), mFd(-1), mZipInflater(null), mBuf(null)
{
mFd = -1;
// Register the Asset with the global list here after it is fully constructed and its
// vtable pointer points to this concrete type.
registerAsset(this);
}
ZipFile zipFile;
String entryName;
// @Override
// public byte[] getBuffer(boolean wordAligned) {
// ZipEntry zipEntry = zipFile.getEntry(entryName);
// int size = (int) zipEntry.getSize();
// byte[] buf = new byte[size];
// try (InputStream in = zipFile.getInputStream(zipEntry)) {
// if (in.read(buf) != size) {
// throw new IOException(
// "Failed to read " + size + " bytes from " + zipFile + "!" + entryName);
// }
// return buf;
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }
/*
* Destructor. Release resources.
*/
@Override
protected 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.
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.
*/
int openChunk(
int fd, long offset, int compressionMethod, int uncompressedLen, int compressedLen) {
throw new UnsupportedOperationException();
// 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.
*/
int openChunk(FileMap dataMap, int 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.]
*/
@Override
public int read(byte[] buf, int bufOffset, int count) {
int maxLen;
int 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 = toIntExact(mUncompressedLen - mOffset);
if (count > maxLen) count = maxLen;
if (!isTruthy(count)) return 0;
/* copy from buffer */
// printf("comp buf read\n");
// memcpy(buf, (String)mBuf + mOffset, count);
System.arraycopy(mBuf, toIntExact(mOffset), buf, bufOffset, 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.
*/
@Override
public long seek(long offset, int whence) {
long newPosn;
// compute new position within chunk
newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
if (newPosn == -1) return newPosn;
// if (mZipInflater) {
// mZipInflater.seekAbsolute(newPosn);
// }
mOffset = newPosn;
return mOffset;
}
/*
* Close the asset.
*/
@Override
public void close() {
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.
*/
@Override
public byte[] getBuffer(boolean wordAligned) {
// return mBuf = mMap.getDataPtr();
byte[] buf = null;
if (mBuf != null) return mBuf;
/*
* Allocate a buffer and read the file into it.
*/
// buf = new byte[(int) mUncompressedLen];
// if (buf == null) {
// ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
// return null;
// }
if (mMap != null) {
buf = mMap.getDataPtr();
// if (!ZipUtils::inflateToBuffer(mMap.getDataPtr(), buf,
// mUncompressedLen, mCompressedLen))
// return null;
} else {
throw new UnsupportedOperationException();
// 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;
}
@Override
public String toString() {
return "_CompressedAsset{" + "mMap=" + mMap + '}';
}
}
// todo: remove when Android supports this
static int toIntExact(long value) {
if ((int) value != value) {
throw new ArithmeticException("integer overflow");
}
return (int) value;
}
}