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