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