Home | History | Annotate | Download | only in android
      1 package org.robolectric.res.android;
      2 
      3 import static org.robolectric.res.android.Asset.AccessMode.ACCESS_BUFFER;
      4 import static org.robolectric.res.android.Errors.NO_ERROR;
      5 import static org.robolectric.res.android.Util.ALOGE;
      6 import static org.robolectric.res.android.Util.ALOGV;
      7 import static org.robolectric.res.android.Util.ALOGW;
      8 import static org.robolectric.res.android.Util.isTruthy;
      9 
     10 import java.io.File;
     11 import java.io.FileDescriptor;
     12 import java.io.FileInputStream;
     13 import java.io.IOException;
     14 import java.io.RandomAccessFile;
     15 import java.util.zip.ZipEntry;
     16 import java.util.zip.ZipFile;
     17 import org.robolectric.res.FileTypedResource;
     18 import org.robolectric.res.FsFile;
     19 
     20 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/Asset.cpp
     21 // and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/Asset.h
     22 /*
     23  * Instances of this class provide read-only operations on a byte stream.
     24  *
     25  * Access may be optimized for streaming, random, or whole buffer modes.  All
     26  * operations are supported regardless of how the file was opened, but some
     27  * things will be less efficient.  [pass that in??]
     28  *
     29  * "Asset" is the base class for all types of assets.  The classes below
     30  * provide most of the implementation.  The AssetManager uses one of the
     31  * static "create" functions defined here to create a new instance.
     32  */
     33 public abstract class Asset {
     34   public static final Asset EXCLUDED_ASSET = new _FileAsset();
     35 
     36   public Runnable onClose;
     37 
     38   public static Asset newFileAsset(FileTypedResource fileTypedResource) throws IOException {
     39     _FileAsset fileAsset = new _FileAsset();
     40     FsFile fsFile = fileTypedResource.getFsFile();
     41     fileAsset.mFileName = fsFile.getName();
     42     fileAsset.mLength = fsFile.length();
     43     fileAsset.mBuf = fsFile.getBytes();
     44     return fileAsset;
     45   }
     46 
     47   // public:
     48   // virtual ~Asset(void) = default;
     49 
     50   // static int getGlobalCount();
     51   // static String8 getAssetAllocations();
     52 
     53   public enum AccessMode {
     54     ACCESS_UNKNOWN(0),
     55     /* read chunks, and seek forward and backward */
     56     ACCESS_RANDOM(1),
     57     /* read sequentially, with an occasional forward seek */
     58     ACCESS_STREAMING(2),
     59     /* caller plans to ask for a read-only buffer with all data */
     60     ACCESS_BUFFER(3);
     61 
     62     private final int mode;
     63 
     64     AccessMode(int mode) {
     65       this.mode = mode;
     66     }
     67 
     68     public int mode() {
     69       return mode;
     70     }
     71 
     72     public static AccessMode fromInt(int mode) {
     73       for (AccessMode enumMode : values()) {
     74         if (mode == enumMode.mode()) {
     75           return enumMode;
     76         }
     77       }
     78       throw new IllegalArgumentException("invalid mode " + Integer.toString(mode));
     79     }
     80   }
     81 
     82   public static final int SEEK_SET = 0;
     83   public static final int SEEK_CUR = 1;
     84   public static final int SEEK_END = 2;
     85 
     86   public final int read(byte[] buf, int count) {
     87     return read(buf, 0, count);
     88   }
     89 
     90   /*
     91    * Read data from the current offset.  Returns the actual number of
     92    * bytes read, 0 on EOF, or -1 on error.
     93    *
     94    * Transliteration note: added bufOffset to translate to: index into buf to start writing at
     95    */
     96   public abstract int read(byte[] buf, int bufOffset, int count);
     97 
     98   /*
     99    * Seek to the specified offset.  "whence" uses the same values as
    100    * lseek/fseek.  Returns the new position on success, or (long) -1
    101    * on failure.
    102    */
    103   public abstract long seek(long offset, int whence);
    104 
    105     /*
    106      * Close the asset, freeing all associated resources.
    107      */
    108     public abstract void close();
    109 
    110     /*
    111      * Get a pointer to a buffer with the entire contents of the file.
    112      */
    113   public abstract byte[] getBuffer(boolean wordAligned);
    114 
    115   /*
    116    * Get the total amount of data that can be read.
    117    */
    118   public abstract long getLength();
    119 
    120   /*
    121    * Get the total amount of data that can be read from the current position.
    122    */
    123   public abstract long getRemainingLength();
    124 
    125     /*
    126      * Open a new file descriptor that can be used to read this asset.
    127      * Returns -1 if you can not use the file descriptor (for example if the
    128      * asset is compressed).
    129      */
    130   public abstract FileDescriptor openFileDescriptor(Ref<Long> outStart, Ref<Long> outLength);
    131 
    132   public abstract File getFile();
    133 
    134   public abstract String getFileName();
    135 
    136   /*
    137    * Return whether this asset's buffer is allocated in RAM (not mmapped).
    138    * Note: not virtual so it is safe to call even when being destroyed.
    139    */
    140   abstract boolean isAllocated(); // { return false; }
    141 
    142   /*
    143    * Get a string identifying the asset's source.  This might be a full
    144    * path, it might be a colon-separated list of identifiers.
    145    *
    146    * This is NOT intended to be used for anything except debug output.
    147    * DO NOT try to parse this or use it to open a file.
    148    */
    149   final String getAssetSource() { return mAssetSource.string(); }
    150 
    151   public abstract boolean isNinePatch();
    152 
    153 //   protected:
    154 //   /*
    155 //    * Adds this Asset to the global Asset list for debugging and
    156 //    * accounting.
    157 //    * Concrete subclasses must call this in their finalructor.
    158 //    */
    159 //   static void registerAsset(Asset asset);
    160 //
    161 //   /*
    162 //    * Removes this Asset from the global Asset list.
    163 //    * Concrete subclasses must call this in their destructor.
    164 //    */
    165 //   static void unregisterAsset(Asset asset);
    166 //
    167 //   Asset(void);        // finalructor; only invoked indirectly
    168 //
    169 //   /* handle common seek() housekeeping */
    170 //   long handleSeek(long offset, int whence, long curPosn, long maxPosn);
    171 
    172   /* set the asset source string */
    173   void setAssetSource(final String8 path) { mAssetSource = path; }
    174 
    175   AccessMode getAccessMode() { return mAccessMode; }
    176 
    177 //   private:
    178 //   /* these operations are not implemented */
    179 //   Asset(final Asset& src);
    180 //   Asset& operator=(final Asset& src);
    181 //
    182 //     /* AssetManager needs access to our "create" functions */
    183 //   friend class AssetManager;
    184 //
    185 //     /*
    186 //      * Create the asset from a named file on disk.
    187 //      */
    188 //   static Asset createFromFile(final String fileName, AccessMode mode);
    189 //
    190 //     /*
    191 //      * Create the asset from a named, compressed file on disk (e.g. ".gz").
    192 //      */
    193 //   static Asset createFromCompressedFile(final String fileName,
    194 //       AccessMode mode);
    195 //
    196 // #if 0
    197 //     /*
    198 //      * Create the asset from a segment of an open file.  This will fail
    199 //      * if "offset" and "length" don't fit within the bounds of the file.
    200 //      *
    201 //      * The asset takes ownership of the file descriptor.
    202 //      */
    203 //   static Asset createFromFileSegment(int fd, long offset, int length,
    204 //       AccessMode mode);
    205 //
    206 //     /*
    207 //      * Create from compressed data.  "fd" should be seeked to the start of
    208 //      * the compressed data.  This could be inside a gzip file or part of a
    209 //      * Zip archive.
    210 //      *
    211 //      * The asset takes ownership of the file descriptor.
    212 //      *
    213 //      * This may not verify the validity of the compressed data until first
    214 //      * use.
    215 //      */
    216 //   static Asset createFromCompressedData(int fd, long offset,
    217 //       int compressionMethod, int compressedLength,
    218 //       int uncompressedLength, AccessMode mode);
    219 // #endif
    220 //
    221 //     /*
    222 //      * Create the asset from a memory-mapped file segment.
    223 //      *
    224 //      * The asset takes ownership of the FileMap.
    225 //      */
    226 //   static Asset createFromUncompressedMap(FileMap dataMap, AccessMode mode);
    227 //
    228 //     /*
    229 //      * Create the asset from a memory-mapped file segment with compressed
    230 //      * data.
    231 //      *
    232 //      * The asset takes ownership of the FileMap.
    233 //      */
    234 //   static Asset createFromCompressedMap(FileMap dataMap,
    235 //       int uncompressedLen, AccessMode mode);
    236 //
    237 //
    238 //     /*
    239 //      * Create from a reference-counted chunk of shared memory.
    240 //      */
    241 //   // TODO
    242 
    243   AccessMode  mAccessMode;        // how the asset was opened
    244   String8    mAssetSource;       // debug string
    245 
    246   Asset		mNext;				// linked list.
    247   Asset		mPrev;
    248 
    249   static final boolean kIsDebug = false;
    250 
    251   final static Object gAssetLock = new Object();
    252   static int gCount = 0;
    253   static Asset gHead = null;
    254   static Asset gTail = null;
    255 
    256   void registerAsset(Asset asset)
    257   {
    258   //   AutoMutex _l(gAssetLock);
    259   //   gCount++;
    260   //   asset.mNext = asset.mPrev = null;
    261   //   if (gTail == null) {
    262   //     gHead = gTail = asset;
    263   //   } else {
    264   //     asset.mPrev = gTail;
    265   //     gTail.mNext = asset;
    266   //     gTail = asset;
    267   //   }
    268   //
    269   //   if (kIsDebug) {
    270   //     ALOGI("Creating Asset %s #%d\n", asset, gCount);
    271   //   }
    272   }
    273 
    274   void unregisterAsset(Asset asset)
    275   {
    276   //   AutoMutex _l(gAssetLock);
    277   //   gCount--;
    278   //   if (gHead == asset) {
    279   //     gHead = asset.mNext;
    280   //   }
    281   //   if (gTail == asset) {
    282   //     gTail = asset.mPrev;
    283   //   }
    284   //   if (asset.mNext != null) {
    285   //     asset.mNext.mPrev = asset.mPrev;
    286   //   }
    287   //   if (asset.mPrev != null) {
    288   //     asset.mPrev.mNext = asset.mNext;
    289   //   }
    290   //   asset.mNext = asset.mPrev = null;
    291   //
    292   //   if (kIsDebug) {
    293   //     ALOGI("Destroying Asset in %s #%d\n", asset, gCount);
    294   //   }
    295   }
    296 
    297   public static int getGlobalCount()
    298   {
    299     // AutoMutex _l(gAssetLock);
    300     synchronized (gAssetLock) {
    301       return gCount;
    302     }
    303   }
    304 
    305   public static String getAssetAllocations()
    306   {
    307     // AutoMutex _l(gAssetLock);
    308     synchronized (gAssetLock) {
    309       StringBuilder res = new StringBuilder();
    310       Asset cur = gHead;
    311       while (cur != null) {
    312         if (cur.isAllocated()) {
    313           res.append("    ");
    314           res.append(cur.getAssetSource());
    315           long size = (cur.getLength()+512)/1024;
    316           String buf = String.format(": %dK\n", (int)size);
    317           res.append(buf);
    318         }
    319         cur = cur.mNext;
    320       }
    321 
    322       return res.toString();
    323     }
    324   }
    325 
    326   Asset() {
    327     // : mAccessMode(ACCESS_UNKNOWN), mNext(null), mPrev(null)
    328     mAccessMode = AccessMode.ACCESS_UNKNOWN;
    329   }
    330 
    331   /*
    332    * Create a new Asset from a file on disk.  There is a fair chance that
    333    * the file doesn't actually exist.
    334    *
    335    * We can use "mode" to decide how we want to go about it.
    336    */
    337   static Asset createFromFile(final String fileName, AccessMode mode)
    338   {
    339     File file = new File(fileName);
    340     if (!file.exists()) {
    341       return null;
    342     }
    343     throw new UnsupportedOperationException();
    344 
    345     // _FileAsset pAsset;
    346     // int result;
    347     // long length;
    348     // int fd;
    349     //
    350     //   fd = open(fileName, O_RDONLY | O_BINARY);
    351     //   if (fd < 0)
    352     //     return null;
    353     //
    354     //   /*
    355     //    * Under Linux, the lseek fails if we actually opened a directory.  To
    356     //    * be correct we should test the file type explicitly, but since we
    357     //    * always open things read-only it doesn't really matter, so there's
    358     //    * no value in incurring the extra overhead of an fstat() call.
    359     //    */
    360     //   // TODO(kroot): replace this with fstat despite the plea above.
    361     //   #if 1
    362     //   length = lseek64(fd, 0, SEEK_END);
    363     //   if (length < 0) {
    364     //   ::close(fd);
    365     //     return null;
    366     //   }
    367     //   (void) lseek64(fd, 0, SEEK_SET);
    368     //   #else
    369     //   struct stat st;
    370     //   if (fstat(fd, &st) < 0) {
    371     //   ::close(fd);
    372     //   return null;
    373     // }
    374     //
    375     //   if (!S_ISREG(st.st_mode)) {
    376     //   ::close(fd);
    377     //     return null;
    378     //   }
    379     //   #endif
    380     //
    381     //     pAsset = new _FileAsset;
    382     //   result = pAsset.openChunk(fileName, fd, 0, length);
    383     //   if (result != NO_ERROR) {
    384     //     delete pAsset;
    385     //     return null;
    386     //   }
    387     //
    388     //   pAsset.mAccessMode = mode;
    389     //   return pAsset;
    390   }
    391 
    392 
    393   /*
    394    * Create a new Asset from a compressed file on disk.  There is a fair chance
    395    * that the file doesn't actually exist.
    396    *
    397    * We currently support gzip files.  We might want to handle .bz2 someday.
    398    */
    399   static Asset createFromCompressedFile(final String fileName,
    400       AccessMode mode)
    401   {
    402     throw new UnsupportedOperationException();
    403     // _CompressedAsset pAsset;
    404     // int result;
    405     // long fileLen;
    406     // boolean scanResult;
    407     // long offset;
    408     // int method;
    409     // long uncompressedLen, compressedLen;
    410     // int fd;
    411     //
    412     // fd = open(fileName, O_RDONLY | O_BINARY);
    413     // if (fd < 0)
    414     //   return null;
    415     //
    416     // fileLen = lseek(fd, 0, SEEK_END);
    417     // if (fileLen < 0) {
    418     // ::close(fd);
    419     //   return null;
    420     // }
    421     // (void) lseek(fd, 0, SEEK_SET);
    422     //
    423     // /* want buffered I/O for the file scan; must dup so fclose() is safe */
    424     // FILE* fp = fdopen(dup(fd), "rb");
    425     // if (fp == null) {
    426     // ::close(fd);
    427     //   return null;
    428     // }
    429     //
    430     // unsigned long crc32;
    431     // scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
    432     // &compressedLen, &crc32);
    433     // offset = ftell(fp);
    434     // fclose(fp);
    435     // if (!scanResult) {
    436     //   ALOGD("File '%s' is not in gzip format\n", fileName);
    437     // ::close(fd);
    438     //   return null;
    439     // }
    440     //
    441     // pAsset = new _CompressedAsset;
    442     // result = pAsset.openChunk(fd, offset, method, uncompressedLen,
    443     //     compressedLen);
    444     // if (result != NO_ERROR) {
    445     //   delete pAsset;
    446     //   return null;
    447     // }
    448     //
    449     // pAsset.mAccessMode = mode;
    450     // return pAsset;
    451   }
    452 
    453 
    454 //     #if 0
    455 // /*
    456 //  * Create a new Asset from part of an open file.
    457 //  */
    458 // /*static*/ Asset createFromFileSegment(int fd, long offset,
    459 //       int length, AccessMode mode)
    460 //   {
    461 //     _FileAsset pAsset;
    462 //     int result;
    463 //
    464 //     pAsset = new _FileAsset;
    465 //     result = pAsset.openChunk(null, fd, offset, length);
    466 //     if (result != NO_ERROR)
    467 //       return null;
    468 //
    469 //     pAsset.mAccessMode = mode;
    470 //     return pAsset;
    471 //   }
    472 //
    473 // /*
    474 //  * Create a new Asset from compressed data in an open file.
    475 //  */
    476 // /*static*/ Asset createFromCompressedData(int fd, long offset,
    477 //       int compressionMethod, int uncompressedLen, int compressedLen,
    478 //       AccessMode mode)
    479 //   {
    480 //     _CompressedAsset pAsset;
    481 //     int result;
    482 //
    483 //     pAsset = new _CompressedAsset;
    484 //     result = pAsset.openChunk(fd, offset, compressionMethod,
    485 //         uncompressedLen, compressedLen);
    486 //     if (result != NO_ERROR)
    487 //       return null;
    488 //
    489 //     pAsset.mAccessMode = mode;
    490 //     return pAsset;
    491 //   }
    492 //     #endif
    493 
    494   /*
    495    * Create a new Asset from a memory mapping.
    496    */
    497   static Asset createFromUncompressedMap(FileMap dataMap,
    498       AccessMode mode)
    499   {
    500     _FileAsset pAsset;
    501     int result;
    502 
    503     pAsset = new _FileAsset();
    504     result = pAsset.openChunk(dataMap);
    505     if (result != NO_ERROR)
    506       return null;
    507 
    508     pAsset.mAccessMode = mode;
    509     return pAsset;
    510   }
    511 
    512   /*
    513    * Create a new Asset from compressed data in a memory mapping.
    514    */
    515 static Asset createFromCompressedMap(FileMap dataMap,
    516       int uncompressedLen, AccessMode mode)
    517   {
    518     _CompressedAsset pAsset;
    519     int result;
    520 
    521     pAsset = new _CompressedAsset();
    522     result = pAsset.openChunk(dataMap, uncompressedLen);
    523     if (result != NO_ERROR)
    524       return null;
    525 
    526     pAsset.mAccessMode = mode;
    527     return pAsset;
    528   }
    529 
    530 
    531   /*
    532    * Do generic seek() housekeeping.  Pass in the offset/whence values from
    533    * the seek request, along with the current chunk offset and the chunk
    534    * length.
    535    *
    536    * Returns the new chunk offset, or -1 if the seek is illegal.
    537    */
    538   long handleSeek(long offset, int whence, long curPosn, long maxPosn)
    539   {
    540     long newOffset;
    541 
    542     switch (whence) {
    543       case SEEK_SET:
    544         newOffset = offset;
    545         break;
    546       case SEEK_CUR:
    547         newOffset = curPosn + offset;
    548         break;
    549       case SEEK_END:
    550         newOffset = maxPosn + offset;
    551         break;
    552       default:
    553         ALOGW("unexpected whence %d\n", whence);
    554         // this was happening due to an long size mismatch
    555         assert(false);
    556         return (long) -1;
    557     }
    558 
    559     if (newOffset < 0 || newOffset > maxPosn) {
    560       ALOGW("seek out of range: want %ld, end=%ld\n",
    561           (long) newOffset, (long) maxPosn);
    562       return (long) -1;
    563     }
    564 
    565     return newOffset;
    566   }
    567 
    568   /*
    569    * An asset based on an uncompressed file on disk.  It may encompass the
    570    * entire file or just a piece of it.  Access is through fread/fseek.
    571    */
    572   static class _FileAsset extends Asset {
    573 
    574     // public:
    575 //     _FileAsset(void);
    576 //     virtual ~_FileAsset(void);
    577 //
    578 //     /*
    579 //      * Use a piece of an already-open file.
    580 //      *
    581 //      * On success, the object takes ownership of "fd".
    582 //      */
    583 //     int openChunk(final String fileName, int fd, long offset, int length);
    584 //
    585 //     /*
    586 //      * Use a memory-mapped region.
    587 //      *
    588 //      * On success, the object takes ownership of "dataMap".
    589 //      */
    590 //     int openChunk(FileMap dataMap);
    591 //
    592 //     /*
    593 //      * Standard Asset interfaces.
    594 //      */
    595 //     virtual ssize_t read(void* buf, int count);
    596 //     virtual long seek(long offset, int whence);
    597 //     virtual void close(void);
    598 //     virtual final void* getBuffer(boolean wordAligned);
    599 
    600     @Override
    601     public long getLength() { return mLength; }
    602 
    603     @Override
    604     public long getRemainingLength() { return mLength-mOffset; }
    605 
    606 //     virtual int openFileDescriptor(long* outStart, long* outLength) final;
    607     @Override
    608     boolean isAllocated() { return mBuf != null; }
    609 
    610     @Override
    611     public boolean isNinePatch() {
    612       String fileName = getFileName();
    613       if (mMap != null) {
    614         fileName = mMap.getZipEntry().getName();
    615       }
    616       return fileName != null && fileName.toLowerCase().endsWith(".9.png");
    617     }
    618 
    619     //
    620 // private:
    621     long mStart;         // absolute file offset of start of chunk
    622     long mLength;        // length of the chunk
    623     long mOffset;        // current local offset, 0 == mStart
    624     // FILE*       mFp;            // for read/seek
    625     RandomAccessFile mFp;            // for read/seek
    626     String mFileName;      // for opening
    627 
    628     /*
    629      * To support getBuffer() we either need to read the entire thing into
    630      * a buffer or memory-map it.  For small files it's probably best to
    631      * just read them in.
    632      */
    633 // enum {
    634   public static int kReadVsMapThreshold = 4096;
    635 // };
    636 
    637     FileMap mMap;           // for memory map
    638     byte[] mBuf;        // for read
    639 
    640     // final void* ensureAlignment(FileMap map);
    641 /*
    642  * ===========================================================================
    643  *      _FileAsset
    644  * ===========================================================================
    645  */
    646 
    647     /*
    648      * Constructor.
    649      */
    650     _FileAsset()
    651     // : mStart(0), mLength(0), mOffset(0), mFp(null), mFileName(null), mMap(null), mBuf(null)
    652     {
    653       // Register the Asset with the global list here after it is fully constructed and its
    654       // vtable pointer points to this concrete type. b/31113965
    655       registerAsset(this);
    656     }
    657 
    658     /*
    659      * Destructor.  Release resources.
    660      */
    661     @Override
    662     protected void finalize() {
    663       close();
    664 
    665       // Unregister the Asset from the global list here before it is destructed and while its vtable
    666       // pointer still points to this concrete type. b/31113965
    667       unregisterAsset(this);
    668     }
    669 
    670     /*
    671      * Operate on a chunk of an uncompressed file.
    672      *
    673      * Zero-length chunks are allowed.
    674      */
    675     int openChunk(final String fileName, int fd, long offset, int length) {
    676       throw new UnsupportedOperationException();
    677       // assert(mFp == null);    // no reopen
    678       // assert(mMap == null);
    679       // assert(fd >= 0);
    680       // assert(offset >= 0);
    681       //
    682       // /*
    683       //  * Seek to end to get file length.
    684       //  */
    685       // long fileLength;
    686       // fileLength = lseek64(fd, 0, SEEK_END);
    687       // if (fileLength == (long) -1) {
    688       //   // probably a bad file descriptor
    689       //   ALOGD("failed lseek (errno=%d)\n", errno);
    690       //   return UNKNOWN_ERROR;
    691       // }
    692       //
    693       // if ((long) (offset + length) > fileLength) {
    694       //   ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
    695       //       (long) offset, (long) length, (long) fileLength);
    696       //   return BAD_INDEX;
    697       // }
    698       //
    699       // /* after fdopen, the fd will be closed on fclose() */
    700       // mFp = fdopen(fd, "rb");
    701       // if (mFp == null)
    702       //   return UNKNOWN_ERROR;
    703       //
    704       // mStart = offset;
    705       // mLength = length;
    706       // assert(mOffset == 0);
    707       //
    708       // /* seek the FILE* to the start of chunk */
    709       // if (fseek(mFp, mStart, SEEK_SET) != 0) {
    710       //   assert(false);
    711       // }
    712       //
    713       // mFileName = fileName != null ? strdup(fileName) : null;
    714       //
    715       // return NO_ERROR;
    716     }
    717 
    718     /*
    719      * Create the chunk from the map.
    720      */
    721     int openChunk(FileMap dataMap) {
    722       assert(mFp == null);    // no reopen
    723       assert(mMap == null);
    724       assert(dataMap != null);
    725 
    726       mMap = dataMap;
    727       mStart = -1;            // not used
    728       mLength = dataMap.getDataLength();
    729       assert(mOffset == 0);
    730 
    731       mBuf = dataMap.getDataPtr();
    732 
    733       return NO_ERROR;
    734     }
    735 
    736     /*
    737      * Read a chunk of data.
    738      */
    739     @Override
    740     public int read(byte[] buf, int bufOffset, int count) {
    741       int maxLen;
    742       int actual;
    743 
    744       assert(mOffset >= 0 && mOffset <= mLength);
    745 
    746       if (getAccessMode() == ACCESS_BUFFER) {
    747           /*
    748            * On first access, read or map the entire file.  The caller has
    749            * requested buffer access, either because they're going to be
    750            * using the buffer or because what they're doing has appropriate
    751            * performance needs and access patterns.
    752            */
    753         if (mBuf == null)
    754           getBuffer(false);
    755       }
    756 
    757       /* adjust count if we're near EOF */
    758       maxLen = toIntExact(mLength - mOffset);
    759       if (count > maxLen)
    760         count = maxLen;
    761 
    762       if (!isTruthy(count)) {
    763         return 0;
    764       }
    765 
    766       if (mMap != null) {
    767           /* copy from mapped area */
    768         //printf("map read\n");
    769         // memcpy(buf, (String)mMap.getDataPtr() + mOffset, count);
    770         System.arraycopy(mMap.getDataPtr(), toIntExact(mOffset), buf, bufOffset, count);
    771         actual = count;
    772       } else if (mBuf != null) {
    773           /* copy from buffer */
    774         //printf("buf read\n");
    775         // memcpy(buf, (String)mBuf + mOffset, count);
    776         System.arraycopy(mBuf, toIntExact(mOffset), buf, bufOffset, count);
    777         actual = count;
    778       } else {
    779           /* read from the file */
    780         //printf("file read\n");
    781         // if (ftell(mFp) != mStart + mOffset) {
    782         try {
    783           if (mFp.getFilePointer() != mStart + mOffset) {
    784             ALOGE("Hosed: %ld != %ld+%ld\n",
    785                 mFp.getFilePointer(), (long) mStart, (long) mOffset);
    786             assert(false);
    787           }
    788 
    789           /*
    790            * This returns 0 on error or eof.  We need to use ferror() or feof()
    791            * to tell the difference, but we don't currently have those on the
    792            * device.  However, we know how much data is *supposed* to be in the
    793            * file, so if we don't read the full amount we know something is
    794            * hosed.
    795            */
    796           actual = mFp.read(buf, 0, count);
    797           if (actual == 0)        // something failed -- I/O error?
    798             return -1;
    799 
    800           assert(actual == count);
    801         } catch (IOException e) {
    802           throw new RuntimeException(e);
    803         }
    804       }
    805 
    806       mOffset += actual;
    807       return actual;
    808     }
    809 
    810     /*
    811      * Seek to a new position.
    812      */
    813     @Override
    814     public long seek(long offset, int whence) {
    815       long newPosn;
    816       long actualOffset;
    817 
    818       // compute new position within chunk
    819       newPosn = handleSeek(offset, whence, mOffset, mLength);
    820       if (newPosn == (long) -1)
    821         return newPosn;
    822 
    823       actualOffset = mStart + newPosn;
    824 
    825       if (mFp != null) {
    826         throw new UnsupportedOperationException();
    827         // if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
    828         //   return (long) -1;
    829       }
    830 
    831       mOffset = actualOffset - mStart;
    832       return mOffset;
    833     }
    834 
    835     /*
    836      * Close the asset.
    837      */
    838     @Override
    839     public void close() {
    840       throw new UnsupportedOperationException();
    841       // if (mMap != null) {
    842       //   delete mMap;
    843       //   mMap = null;
    844       // }
    845       // if (mBuf != null) {
    846       //   delete[] mBuf;
    847       //   mBuf = null;
    848       // }
    849       //
    850       // if (mFileName != null) {
    851       //   free(mFileName);
    852       //   mFileName = null;
    853       // }
    854       //
    855       // if (mFp != null) {
    856       //   // can only be null when called from destructor
    857       //   // (otherwise we would never return this object)
    858       //   fclose(mFp);
    859       //   mFp = null;
    860       // }
    861     }
    862 
    863     /*
    864      * Return a read-only pointer to a buffer.
    865      *
    866      * We can either read the whole thing in or map the relevant piece of
    867      * the source file.  Ideally a map would be established at a higher
    868      * level and we'd be using a different object, but we didn't, so we
    869      * deal with it here.
    870      */
    871     @Override
    872     public final byte[] getBuffer(boolean wordAligned) {
    873       /* subsequent requests just use what we did previously */
    874       if (mBuf != null)
    875         return mBuf;
    876       if (mMap != null) {
    877         // if (!wordAligned) {
    878           return  mMap.getDataPtr();
    879         // }
    880         // return ensureAlignment(mMap);
    881       }
    882 
    883       // assert(mFp != null);
    884 
    885       if (true /*mLength < kReadVsMapThreshold*/) {
    886         byte[] buf;
    887         int allocLen;
    888 
    889           /* zero-length files are allowed; not sure about zero-len allocs */
    890           /* (works fine with gcc + x86linux) */
    891         allocLen = toIntExact(mLength);
    892         if (mLength == 0)
    893           allocLen = 1;
    894 
    895         buf = new byte[allocLen];
    896         if (buf == null) {
    897           ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
    898           return null;
    899         }
    900 
    901         ALOGV("Asset %s allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
    902         if (mLength > 0) {
    903           try {
    904             // long oldPosn = ftell(mFp);
    905             long oldPosn = mFp.getFilePointer();
    906             // fseek(mFp, mStart, SEEK_SET);
    907             mFp.seek(mStart);
    908             // if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
    909             if (mFp.read(buf, 0, toIntExact(mLength)) != (int) mLength) {
    910               ALOGE("failed reading %ld bytes\n", (long) mLength);
    911               // delete[] buf;
    912               return null;
    913             }
    914             // fseek(mFp, oldPosn, SEEK_SET);
    915             mFp.seek(oldPosn);
    916           } catch (IOException e) {
    917             throw new RuntimeException(e);
    918           }
    919         }
    920 
    921         ALOGV(" getBuffer: loaded into buffer\n");
    922 
    923         mBuf = buf;
    924         return mBuf;
    925       } else {
    926         FileMap map;
    927 
    928         map = new FileMap();
    929         // if (!map.create(null, fileno(mFp), mStart, mLength, true)) {
    930         if (!map.create(null, -1, mStart, toIntExact(mLength), true)) {
    931           // delete map;
    932           return null;
    933         }
    934 
    935         ALOGV(" getBuffer: mapped\n");
    936 
    937         mMap = map;
    938         // if (!wordAligned) {
    939         //   return  mMap.getDataPtr();
    940         // }
    941         return ensureAlignment(mMap);
    942       }
    943     }
    944 
    945     /**
    946      * Return the file on disk representing this asset.
    947      *
    948      * Non-Android framework method. Based on {@link #openFileDescriptor(Ref, Ref)}.
    949      */
    950     @Override
    951     public File getFile() {
    952       if (mMap != null) {
    953         String fname = mMap.getFileName();
    954         if (fname == null) {
    955           fname = mFileName;
    956         }
    957         if (fname == null) {
    958           return null;
    959         }
    960         // return open(fname, O_RDONLY | O_BINARY);
    961         return new File(fname);
    962       }
    963       if (mFileName == null) {
    964         return null;
    965       }
    966       return new File(mFileName);
    967     }
    968 
    969     @Override
    970     public String getFileName() {
    971       File file = getFile();
    972       return file == null ? null : file.getName();
    973     }
    974 
    975     @Override
    976     public FileDescriptor openFileDescriptor(Ref<Long> outStart, Ref<Long> outLength) {
    977       if (mMap != null) {
    978         String fname = mMap.getFileName();
    979         if (fname == null) {
    980           fname = mFileName;
    981         }
    982         if (fname == null) {
    983           return null;
    984         }
    985         outStart.set(mMap.getDataOffset());
    986         outLength.set((long) mMap.getDataLength());
    987         // return open(fname, O_RDONLY | O_BINARY);
    988         return open(fname);
    989       }
    990       if (mFileName == null) {
    991         return null;
    992       }
    993       outStart.set(mStart);
    994       outLength.set(mLength);
    995       // return open(mFileName, O_RDONLY | O_BINARY);
    996       return open(mFileName);
    997     }
    998 
    999     private static FileDescriptor open(String fname) {
   1000       try {
   1001         return new FileInputStream(new File(fname)).getFD();
   1002       } catch (IOException e) {
   1003         return null;
   1004       }
   1005     }
   1006 
   1007     final byte[] ensureAlignment(FileMap map) {
   1008       throw new UnsupportedOperationException();
   1009       //   void* data = map.getDataPtr();
   1010       //   if ((((int)data)&0x3) == 0) {
   1011       //     // We can return this directly if it is aligned on a word
   1012       //     // boundary.
   1013       //     ALOGV("Returning aligned FileAsset %s (%s).", this,
   1014       //         getAssetSource());
   1015       //     return data;
   1016       //   }
   1017       //   // If not aligned on a word boundary, then we need to copy it into
   1018       //   // our own buffer.
   1019       //   ALOGV("Copying FileAsset %s (%s) to buffer size %d to make it aligned.", this,
   1020       //       getAssetSource(), (int)mLength);
   1021       //   unsigned String buf = new unsigned char[mLength];
   1022       //   if (buf == null) {
   1023       //     ALOGE("alloc of %ld bytes failed\n", (long) mLength);
   1024       //     return null;
   1025       //   }
   1026       //   memcpy(buf, data, mLength);
   1027       //   mBuf = buf;
   1028       //   return buf;
   1029       // }
   1030     }
   1031 
   1032     @Override
   1033     public String toString() {
   1034       if (mFileName == null) {
   1035         return "_FileAsset{" +
   1036             "mMap=" + mMap +
   1037             '}';
   1038       } else {
   1039         return "_FileAsset{" +
   1040             "mFileName='" + mFileName + '\'' +
   1041             '}';
   1042       }
   1043     }
   1044   }
   1045 
   1046   /*
   1047    * An asset based on compressed data in a file.
   1048    */
   1049   static class _CompressedAsset extends Asset {
   1050 // public:
   1051 //     _CompressedAsset(void);
   1052 //     virtual ~_CompressedAsset(void);
   1053 //
   1054 //     /*
   1055 //      * Use a piece of an already-open file.
   1056 //      *
   1057 //      * On success, the object takes ownership of "fd".
   1058 //      */
   1059 //     int openChunk(int fd, long offset, int compressionMethod,
   1060 //     int uncompressedLen, int compressedLen);
   1061 //
   1062 //     /*
   1063 //      * Use a memory-mapped region.
   1064 //      *
   1065 //      * On success, the object takes ownership of "fd".
   1066 //      */
   1067 //     int openChunk(FileMap dataMap, int uncompressedLen);
   1068 //
   1069 //     /*
   1070 //      * Standard Asset interfaces.
   1071 //      */
   1072 //     virtual ssize_t read(void* buf, int count);
   1073 //     virtual long seek(long offset, int whence);
   1074 //     virtual void close(void);
   1075 //     virtual final void* getBuffer(boolean wordAligned);
   1076 
   1077     @Override
   1078     public long getLength() { return mUncompressedLen; }
   1079 
   1080     @Override
   1081     public long getRemainingLength() { return mUncompressedLen-mOffset; }
   1082 
   1083     @Override
   1084     public File getFile() {
   1085       return null;
   1086     }
   1087 
   1088     @Override
   1089     public String getFileName() {
   1090       ZipEntry zipEntry = mMap.getZipEntry();
   1091       return zipEntry == null ? null : zipEntry.getName();
   1092     }
   1093 
   1094     @Override
   1095     public FileDescriptor openFileDescriptor(Ref<Long> outStart, Ref<Long> outLength) { return null; }
   1096 
   1097     @Override
   1098     boolean isAllocated() { return mBuf != null; }
   1099 
   1100     @Override
   1101     public boolean isNinePatch() {
   1102       String fileName = getFileName();
   1103       return fileName != null && fileName.toLowerCase().endsWith(".9.png");
   1104     }
   1105 
   1106     // private:
   1107     long mStart;         // offset to start of compressed data
   1108     long mCompressedLen; // length of the compressed data
   1109     long mUncompressedLen; // length of the uncompressed data
   1110     long mOffset;        // current offset, 0 == start of uncomp data
   1111 
   1112     FileMap mMap;           // for memory-mapped input
   1113     int mFd;            // for file input
   1114 
   1115 // class StreamingZipInflater mZipInflater;  // for streaming large compressed assets
   1116 
   1117     byte[] mBuf;       // for getBuffer()
   1118 /*
   1119  * ===========================================================================
   1120  *      _CompressedAsset
   1121  * ===========================================================================
   1122  */
   1123 
   1124     /*
   1125      * Constructor.
   1126      */
   1127     _CompressedAsset()
   1128     // : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
   1129     // mMap(null), mFd(-1), mZipInflater(null), mBuf(null)
   1130     {
   1131       mFd = -1;
   1132 
   1133       // Register the Asset with the global list here after it is fully constructed and its
   1134       // vtable pointer points to this concrete type. b/31113965
   1135       registerAsset(this);
   1136     }
   1137 
   1138     ZipFile zipFile;
   1139     String entryName;
   1140 
   1141     // @Override
   1142     // public byte[] getBuffer(boolean wordAligned) {
   1143     //   ZipEntry zipEntry = zipFile.getEntry(entryName);
   1144     //   int size = (int) zipEntry.getSize();
   1145     //   byte[] buf = new byte[size];
   1146     //   try (InputStream in = zipFile.getInputStream(zipEntry)) {
   1147     //     if (in.read(buf) != size) {
   1148     //       throw new IOException(
   1149     //           "Failed to read " + size + " bytes from " + zipFile + "!" + entryName);
   1150     //     }
   1151     //     return buf;
   1152     //   } catch (IOException e) {
   1153     //     throw new RuntimeException(e);
   1154     //   }
   1155     // }
   1156 
   1157     /*
   1158      * Destructor.  Release resources.
   1159      */
   1160     @Override
   1161     protected void finalize() {
   1162       close();
   1163 
   1164       // Unregister the Asset from the global list here before it is destructed and while its vtable
   1165       // pointer still points to this concrete type. b/31113965
   1166       unregisterAsset(this);
   1167     }
   1168 
   1169     /*
   1170      * Open a chunk of compressed data inside a file.
   1171      *
   1172      * This currently just sets up some values and returns.  On the first
   1173      * read, we expand the entire file into a buffer and return data from it.
   1174      */
   1175     int openChunk(int fd, long offset,
   1176         int compressionMethod, int uncompressedLen, int compressedLen) {
   1177       throw new UnsupportedOperationException();
   1178       // assert(mFd < 0);        // no re-open
   1179       // assert(mMap == null);
   1180       // assert(fd >= 0);
   1181       // assert(offset >= 0);
   1182       // assert(compressedLen > 0);
   1183       //
   1184       // if (compressionMethod != ZipFileRO::kCompressDeflated) {
   1185       // assert(false);
   1186       // return UNKNOWN_ERROR;
   1187       // }
   1188       //
   1189       // mStart = offset;
   1190       // mCompressedLen = compressedLen;
   1191       // mUncompressedLen = uncompressedLen;
   1192       // assert(mOffset == 0);
   1193       // mFd = fd;
   1194       // assert(mBuf == null);
   1195       //
   1196       // if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
   1197       // mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
   1198       // }
   1199       //
   1200       // return NO_ERROR;
   1201     }
   1202 
   1203     /*
   1204      * Open a chunk of compressed data in a mapped region.
   1205      *
   1206      * Nothing is expanded until the first read call.
   1207      */
   1208     int openChunk(FileMap dataMap, int uncompressedLen) {
   1209       assert(mFd < 0);        // no re-open
   1210       assert(mMap == null);
   1211       assert(dataMap != null);
   1212 
   1213       mMap = dataMap;
   1214       mStart = -1;        // not used
   1215       mCompressedLen = dataMap.getDataLength();
   1216       mUncompressedLen = uncompressedLen;
   1217       assert(mOffset == 0);
   1218 
   1219       // if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
   1220       // mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
   1221       // }
   1222       return NO_ERROR;
   1223     }
   1224 
   1225     /*
   1226      * Read data from a chunk of compressed data.
   1227      *
   1228      * [For now, that's just copying data out of a buffer.]
   1229      */
   1230     @Override
   1231     public int read(byte[] buf, int bufOffset, int count) {
   1232       int maxLen;
   1233       int actual;
   1234 
   1235       assert(mOffset >= 0 && mOffset <= mUncompressedLen);
   1236 
   1237        /* If we're relying on a streaming inflater, go through that */
   1238 //       if (mZipInflater) {
   1239 //       actual = mZipInflater.read(buf, count);
   1240 //       } else {
   1241       if (mBuf == null) {
   1242         if (getBuffer(false) == null)
   1243           return -1;
   1244       }
   1245       assert(mBuf != null);
   1246 
   1247       /* adjust count if we're near EOF */
   1248       maxLen = toIntExact(mUncompressedLen - mOffset);
   1249       if (count > maxLen)
   1250         count = maxLen;
   1251 
   1252       if (!isTruthy(count))
   1253         return 0;
   1254 
   1255       /* copy from buffer */
   1256       //printf("comp buf read\n");
   1257 //      memcpy(buf, (String)mBuf + mOffset, count);
   1258       System.arraycopy(mBuf, toIntExact(mOffset), buf, bufOffset, count);
   1259       actual = count;
   1260 //       }
   1261 
   1262       mOffset += actual;
   1263       return actual;
   1264     }
   1265 
   1266     /*
   1267      * Handle a seek request.
   1268      *
   1269      * If we're working in a streaming mode, this is going to be fairly
   1270      * expensive, because it requires plowing through a bunch of compressed
   1271      * data.
   1272      */
   1273     @Override
   1274     public long seek(long offset, int whence) {
   1275       long newPosn;
   1276 
   1277       // compute new position within chunk
   1278       newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
   1279       if (newPosn == (long) -1)
   1280       return newPosn;
   1281 
   1282       // if (mZipInflater) {
   1283       //   mZipInflater.seekAbsolute(newPosn);
   1284       // }
   1285       mOffset = newPosn;
   1286       return mOffset;
   1287     }
   1288 
   1289     /*
   1290      * Close the asset.
   1291      */
   1292     @Override
   1293     public void close() {
   1294        if (mMap != null) {
   1295 //       delete mMap;
   1296        mMap = null;
   1297        }
   1298 
   1299 //       delete[] mBuf;
   1300        mBuf = null;
   1301 
   1302 //       delete mZipInflater;
   1303 //       mZipInflater = null;
   1304 
   1305        if (mFd > 0) {
   1306 //       ::close(mFd);
   1307        mFd = -1;
   1308        }
   1309     }
   1310 
   1311     /*
   1312      * Get a pointer to a read-only buffer of data.
   1313      *
   1314      * The first time this is called, we expand the compressed data into a
   1315      * buffer.
   1316      */
   1317     @Override
   1318     public byte[] getBuffer(boolean wordAligned) {
   1319       // return mBuf = mMap.getDataPtr();
   1320       byte[] buf = null;
   1321 
   1322       if (mBuf != null)
   1323         return mBuf;
   1324 
   1325       /*
   1326        * Allocate a buffer and read the file into it.
   1327        */
   1328       // buf = new byte[(int) mUncompressedLen];
   1329       // if (buf == null) {
   1330       //   ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
   1331       //   return null;
   1332       // }
   1333 
   1334       if (mMap != null) {
   1335         buf = mMap.getDataPtr();
   1336         // if (!ZipUtils::inflateToBuffer(mMap.getDataPtr(), buf,
   1337         //     mUncompressedLen, mCompressedLen))
   1338         // return null;
   1339       } else {
   1340         throw new UnsupportedOperationException();
   1341         // assert(mFd >= 0);
   1342         //
   1343         // /*
   1344         //    * Seek to the start of the compressed data.
   1345         //    */
   1346         // if (lseek(mFd, mStart, SEEK_SET) != mStart)
   1347         // goto bail;
   1348         //
   1349         // /*
   1350         //    * Expand the data into it.
   1351         //    */
   1352         // if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
   1353         //     mCompressedLen))
   1354         // goto bail;
   1355       }
   1356 
   1357       /*
   1358        * Success - now that we have the full asset in RAM we
   1359        * no longer need the streaming inflater
   1360        */
   1361       // delete mZipInflater;
   1362       // mZipInflater = null;
   1363 
   1364       mBuf = buf;
   1365       // buf = null;
   1366 
   1367       // bail:
   1368       // delete[] buf;
   1369       return mBuf;
   1370     }
   1371 
   1372     @Override
   1373     public String toString() {
   1374       return "_CompressedAsset{" +
   1375           "mMap=" + mMap +
   1376           '}';
   1377     }
   1378   }
   1379 
   1380   // todo: remove when Android supports this
   1381   static int toIntExact(long value) {
   1382     if ((int)value != value) {
   1383       throw new ArithmeticException("integer overflow");
   1384     }
   1385     return (int)value;
   1386   }
   1387 }
   1388