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