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