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