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 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 64 AccessMode(int mode) { 65 this.mode = mode; 66 } 67 68 public int mode() { 69 return mode; 70 } 71 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 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 */ 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 */ 103 public abstract long seek(long offset, int whence); 104 105 /* 106 * Close the asset, freeing all associated resources. 107 */ 108 public abstract void close(); 109 110 /* 111 * Get a pointer to a buffer with the entire contents of the file. 112 */ 113 public abstract byte[] getBuffer(boolean wordAligned); 114 115 /* 116 * Get the total amount of data that can be read. 117 */ 118 public abstract long getLength(); 119 120 /* 121 * Get the total amount of data that can be read from the current position. 122 */ 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 */ 130 public abstract FileDescriptor openFileDescriptor(Ref<Long> outStart, Ref<Long> outLength); 131 132 public abstract File getFile(); 133 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 */ 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 */ 149 final String getAssetSource() { return mAssetSource.string(); } 150 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 */ 173 void setAssetSource(final String8 path) { mAssetSource = path; } 174 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 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 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 297 public static int getGlobalCount() 298 { 299 // AutoMutex _l(gAssetLock); 300 synchronized (gAssetLock) { 301 return gCount; 302 } 303 } 304 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 601 public long getLength() { return mLength; } 602 603 @Override 604 public long getRemainingLength() { return mLength-mOffset; } 605 606 // virtual int openFileDescriptor(long* outStart, long* outLength) final; 607 @Override 608 boolean isAllocated() { return mBuf != null; } 609 610 @Override 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 */ 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 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 */ 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 */ 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 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 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 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 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 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 970 public String getFileName() { 971 File file = getFile(); 972 return file == null ? null : file.getName(); 973 } 974 975 @Override 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 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 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 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 1078 public long getLength() { return mUncompressedLen; } 1079 1080 @Override 1081 public long getRemainingLength() { return mUncompressedLen-mOffset; } 1082 1083 @Override 1084 public File getFile() { 1085 return null; 1086 } 1087 1088 @Override 1089 public String getFileName() { 1090 ZipEntry zipEntry = mMap.getZipEntry(); 1091 return zipEntry == null ? null : zipEntry.getName(); 1092 } 1093 1094 @Override 1095 public FileDescriptor openFileDescriptor(Ref<Long> outStart, Ref<Long> outLength) { return null; } 1096 1097 @Override 1098 boolean isAllocated() { return mBuf != null; } 1099 1100 @Override 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 */ 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 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 */ 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 */ 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 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 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 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 1373 public String toString() { 1374 return "_CompressedAsset{" + 1375 "mMap=" + mMap + 1376 '}'; 1377 } 1378 } 1379 1380 // todo: remove when Android supports this 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