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