1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // 18 // Provide access to read-only assets. 19 // 20 21 #define LOG_TAG "asset" 22 //#define LOG_NDEBUG 0 23 24 #include <androidfw/Asset.h> 25 #include <androidfw/AssetDir.h> 26 #include <androidfw/AssetManager.h> 27 #include <androidfw/ResourceTypes.h> 28 #include <utils/Atomic.h> 29 #include <utils/Log.h> 30 #include <utils/String8.h> 31 #include <utils/String8.h> 32 #include <utils/threads.h> 33 #include <utils/Timers.h> 34 #include <utils/ZipFileRO.h> 35 36 #include <assert.h> 37 #include <dirent.h> 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <strings.h> 41 #include <sys/stat.h> 42 #include <unistd.h> 43 44 #ifndef TEMP_FAILURE_RETRY 45 /* Used to retry syscalls that can return EINTR. */ 46 #define TEMP_FAILURE_RETRY(exp) ({ \ 47 typeof (exp) _rc; \ 48 do { \ 49 _rc = (exp); \ 50 } while (_rc == -1 && errno == EINTR); \ 51 _rc; }) 52 #endif 53 54 using namespace android; 55 56 /* 57 * Names for default app, locale, and vendor. We might want to change 58 * these to be an actual locale, e.g. always use en-US as the default. 59 */ 60 static const char* kDefaultLocale = "default"; 61 static const char* kDefaultVendor = "default"; 62 static const char* kAssetsRoot = "assets"; 63 static const char* kAppZipName = NULL; //"classes.jar"; 64 static const char* kSystemAssets = "framework/framework-res.apk"; 65 static const char* kIdmapCacheDir = "resource-cache"; 66 67 static const char* kExcludeExtension = ".EXCLUDE"; 68 69 static Asset* const kExcludedAsset = (Asset*) 0xd000000d; 70 71 static volatile int32_t gCount = 0; 72 73 namespace { 74 // Transform string /a/b/c.apk to /data/resource-cache/a@b (at) c.apk@idmap 75 String8 idmapPathForPackagePath(const String8& pkgPath) 76 { 77 const char* root = getenv("ANDROID_DATA"); 78 LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set"); 79 String8 path(root); 80 path.appendPath(kIdmapCacheDir); 81 82 char buf[256]; // 256 chars should be enough for anyone... 83 strncpy(buf, pkgPath.string(), 255); 84 buf[255] = '\0'; 85 char* filename = buf; 86 while (*filename && *filename == '/') { 87 ++filename; 88 } 89 char* p = filename; 90 while (*p) { 91 if (*p == '/') { 92 *p = '@'; 93 } 94 ++p; 95 } 96 path.appendPath(filename); 97 path.append("@idmap"); 98 99 return path; 100 } 101 102 /* 103 * Like strdup(), but uses C++ "new" operator instead of malloc. 104 */ 105 static char* strdupNew(const char* str) 106 { 107 char* newStr; 108 int len; 109 110 if (str == NULL) 111 return NULL; 112 113 len = strlen(str); 114 newStr = new char[len+1]; 115 memcpy(newStr, str, len+1); 116 117 return newStr; 118 } 119 } 120 121 /* 122 * =========================================================================== 123 * AssetManager 124 * =========================================================================== 125 */ 126 127 int32_t AssetManager::getGlobalCount() 128 { 129 return gCount; 130 } 131 132 AssetManager::AssetManager(CacheMode cacheMode) 133 : mLocale(NULL), mVendor(NULL), 134 mResources(NULL), mConfig(new ResTable_config), 135 mCacheMode(cacheMode), mCacheValid(false) 136 { 137 int count = android_atomic_inc(&gCount)+1; 138 //ALOGI("Creating AssetManager %p #%d\n", this, count); 139 memset(mConfig, 0, sizeof(ResTable_config)); 140 } 141 142 AssetManager::~AssetManager(void) 143 { 144 int count = android_atomic_dec(&gCount); 145 //ALOGI("Destroying AssetManager in %p #%d\n", this, count); 146 147 delete mConfig; 148 delete mResources; 149 150 // don't have a String class yet, so make sure we clean up 151 delete[] mLocale; 152 delete[] mVendor; 153 } 154 155 bool AssetManager::addAssetPath(const String8& path, void** cookie) 156 { 157 AutoMutex _l(mLock); 158 159 asset_path ap; 160 161 String8 realPath(path); 162 if (kAppZipName) { 163 realPath.appendPath(kAppZipName); 164 } 165 ap.type = ::getFileType(realPath.string()); 166 if (ap.type == kFileTypeRegular) { 167 ap.path = realPath; 168 } else { 169 ap.path = path; 170 ap.type = ::getFileType(path.string()); 171 if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) { 172 ALOGW("Asset path %s is neither a directory nor file (type=%d).", 173 path.string(), (int)ap.type); 174 return false; 175 } 176 } 177 178 // Skip if we have it already. 179 for (size_t i=0; i<mAssetPaths.size(); i++) { 180 if (mAssetPaths[i].path == ap.path) { 181 if (cookie) { 182 *cookie = (void*)(i+1); 183 } 184 return true; 185 } 186 } 187 188 ALOGV("In %p Asset %s path: %s", this, 189 ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); 190 191 mAssetPaths.add(ap); 192 193 // new paths are always added at the end 194 if (cookie) { 195 *cookie = (void*)mAssetPaths.size(); 196 } 197 198 // add overlay packages for /system/framework; apps are handled by the 199 // (Java) package manager 200 if (strncmp(path.string(), "/system/framework/", 18) == 0) { 201 // When there is an environment variable for /vendor, this 202 // should be changed to something similar to how ANDROID_ROOT 203 // and ANDROID_DATA are used in this file. 204 String8 overlayPath("/vendor/overlay/framework/"); 205 overlayPath.append(path.getPathLeaf()); 206 if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) { 207 asset_path oap; 208 oap.path = overlayPath; 209 oap.type = ::getFileType(overlayPath.string()); 210 bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay 211 if (addOverlay) { 212 oap.idmap = idmapPathForPackagePath(overlayPath); 213 214 if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) { 215 addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap); 216 } 217 } 218 if (addOverlay) { 219 mAssetPaths.add(oap); 220 } else { 221 ALOGW("failed to add overlay package %s\n", overlayPath.string()); 222 } 223 } 224 } 225 226 return true; 227 } 228 229 bool AssetManager::isIdmapStaleLocked(const String8& originalPath, const String8& overlayPath, 230 const String8& idmapPath) 231 { 232 struct stat st; 233 if (TEMP_FAILURE_RETRY(stat(idmapPath.string(), &st)) == -1) { 234 if (errno == ENOENT) { 235 return true; // non-existing idmap is always stale 236 } else { 237 ALOGW("failed to stat file %s: %s\n", idmapPath.string(), strerror(errno)); 238 return false; 239 } 240 } 241 if (st.st_size < ResTable::IDMAP_HEADER_SIZE_BYTES) { 242 ALOGW("file %s has unexpectedly small size=%zd\n", idmapPath.string(), (size_t)st.st_size); 243 return false; 244 } 245 int fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_RDONLY)); 246 if (fd == -1) { 247 ALOGW("failed to open file %s: %s\n", idmapPath.string(), strerror(errno)); 248 return false; 249 } 250 char buf[ResTable::IDMAP_HEADER_SIZE_BYTES]; 251 ssize_t bytesLeft = ResTable::IDMAP_HEADER_SIZE_BYTES; 252 for (;;) { 253 ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf + ResTable::IDMAP_HEADER_SIZE_BYTES - bytesLeft, 254 bytesLeft)); 255 if (r < 0) { 256 TEMP_FAILURE_RETRY(close(fd)); 257 return false; 258 } 259 bytesLeft -= r; 260 if (bytesLeft == 0) { 261 break; 262 } 263 } 264 TEMP_FAILURE_RETRY(close(fd)); 265 266 uint32_t cachedOriginalCrc, cachedOverlayCrc; 267 if (!ResTable::getIdmapInfo(buf, ResTable::IDMAP_HEADER_SIZE_BYTES, 268 &cachedOriginalCrc, &cachedOverlayCrc)) { 269 return false; 270 } 271 272 uint32_t actualOriginalCrc, actualOverlayCrc; 273 if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &actualOriginalCrc)) { 274 return false; 275 } 276 if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &actualOverlayCrc)) { 277 return false; 278 } 279 return cachedOriginalCrc != actualOriginalCrc || cachedOverlayCrc != actualOverlayCrc; 280 } 281 282 bool AssetManager::getZipEntryCrcLocked(const String8& zipPath, const char* entryFilename, 283 uint32_t* pCrc) 284 { 285 asset_path ap; 286 ap.path = zipPath; 287 const ZipFileRO* zip = getZipFileLocked(ap); 288 if (zip == NULL) { 289 return false; 290 } 291 const ZipEntryRO entry = zip->findEntryByName(entryFilename); 292 if (entry == NULL) { 293 return false; 294 } 295 if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, (long*)pCrc)) { 296 return false; 297 } 298 return true; 299 } 300 301 bool AssetManager::createIdmapFileLocked(const String8& originalPath, const String8& overlayPath, 302 const String8& idmapPath) 303 { 304 ALOGD("%s: originalPath=%s overlayPath=%s idmapPath=%s\n", 305 __FUNCTION__, originalPath.string(), overlayPath.string(), idmapPath.string()); 306 ResTable tables[2]; 307 const String8* paths[2] = { &originalPath, &overlayPath }; 308 uint32_t originalCrc, overlayCrc; 309 bool retval = false; 310 ssize_t offset = 0; 311 int fd = 0; 312 uint32_t* data = NULL; 313 size_t size; 314 315 for (int i = 0; i < 2; ++i) { 316 asset_path ap; 317 ap.type = kFileTypeRegular; 318 ap.path = *paths[i]; 319 Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); 320 if (ass == NULL) { 321 ALOGW("failed to find resources.arsc in %s\n", ap.path.string()); 322 goto error; 323 } 324 tables[i].add(ass, (void*)1, false); 325 } 326 327 if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &originalCrc)) { 328 ALOGW("failed to retrieve crc for resources.arsc in %s\n", originalPath.string()); 329 goto error; 330 } 331 if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &overlayCrc)) { 332 ALOGW("failed to retrieve crc for resources.arsc in %s\n", overlayPath.string()); 333 goto error; 334 } 335 336 if (tables[0].createIdmap(tables[1], originalCrc, overlayCrc, 337 (void**)&data, &size) != NO_ERROR) { 338 ALOGW("failed to generate idmap data for file %s\n", idmapPath.string()); 339 goto error; 340 } 341 342 // This should be abstracted (eg replaced by a stand-alone 343 // application like dexopt, triggered by something equivalent to 344 // installd). 345 fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_WRONLY | O_CREAT | O_TRUNC, 0644)); 346 if (fd == -1) { 347 ALOGW("failed to write idmap file %s (open: %s)\n", idmapPath.string(), strerror(errno)); 348 goto error_free; 349 } 350 for (;;) { 351 ssize_t written = TEMP_FAILURE_RETRY(write(fd, data + offset, size)); 352 if (written < 0) { 353 ALOGW("failed to write idmap file %s (write: %s)\n", idmapPath.string(), 354 strerror(errno)); 355 goto error_close; 356 } 357 size -= (size_t)written; 358 offset += written; 359 if (size == 0) { 360 break; 361 } 362 } 363 364 retval = true; 365 error_close: 366 TEMP_FAILURE_RETRY(close(fd)); 367 error_free: 368 free(data); 369 error: 370 return retval; 371 } 372 373 bool AssetManager::addDefaultAssets() 374 { 375 const char* root = getenv("ANDROID_ROOT"); 376 LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set"); 377 378 String8 path(root); 379 path.appendPath(kSystemAssets); 380 381 return addAssetPath(path, NULL); 382 } 383 384 void* AssetManager::nextAssetPath(void* cookie) const 385 { 386 AutoMutex _l(mLock); 387 size_t next = ((size_t)cookie)+1; 388 return next > mAssetPaths.size() ? NULL : (void*)next; 389 } 390 391 String8 AssetManager::getAssetPath(void* cookie) const 392 { 393 AutoMutex _l(mLock); 394 const size_t which = ((size_t)cookie)-1; 395 if (which < mAssetPaths.size()) { 396 return mAssetPaths[which].path; 397 } 398 return String8(); 399 } 400 401 /* 402 * Set the current locale. Use NULL to indicate no locale. 403 * 404 * Close and reopen Zip archives as appropriate, and reset cached 405 * information in the locale-specific sections of the tree. 406 */ 407 void AssetManager::setLocale(const char* locale) 408 { 409 AutoMutex _l(mLock); 410 setLocaleLocked(locale); 411 } 412 413 void AssetManager::setLocaleLocked(const char* locale) 414 { 415 if (mLocale != NULL) { 416 /* previously set, purge cached data */ 417 purgeFileNameCacheLocked(); 418 //mZipSet.purgeLocale(); 419 delete[] mLocale; 420 } 421 mLocale = strdupNew(locale); 422 423 updateResourceParamsLocked(); 424 } 425 426 /* 427 * Set the current vendor. Use NULL to indicate no vendor. 428 * 429 * Close and reopen Zip archives as appropriate, and reset cached 430 * information in the vendor-specific sections of the tree. 431 */ 432 void AssetManager::setVendor(const char* vendor) 433 { 434 AutoMutex _l(mLock); 435 436 if (mVendor != NULL) { 437 /* previously set, purge cached data */ 438 purgeFileNameCacheLocked(); 439 //mZipSet.purgeVendor(); 440 delete[] mVendor; 441 } 442 mVendor = strdupNew(vendor); 443 } 444 445 void AssetManager::setConfiguration(const ResTable_config& config, const char* locale) 446 { 447 AutoMutex _l(mLock); 448 *mConfig = config; 449 if (locale) { 450 setLocaleLocked(locale); 451 } else if (config.language[0] != 0) { 452 char spec[9]; 453 spec[0] = config.language[0]; 454 spec[1] = config.language[1]; 455 if (config.country[0] != 0) { 456 spec[2] = '_'; 457 spec[3] = config.country[0]; 458 spec[4] = config.country[1]; 459 spec[5] = 0; 460 } else { 461 spec[3] = 0; 462 } 463 setLocaleLocked(spec); 464 } else { 465 updateResourceParamsLocked(); 466 } 467 } 468 469 void AssetManager::getConfiguration(ResTable_config* outConfig) const 470 { 471 AutoMutex _l(mLock); 472 *outConfig = *mConfig; 473 } 474 475 /* 476 * Open an asset. 477 * 478 * The data could be; 479 * - In a file on disk (assetBase + fileName). 480 * - In a compressed file on disk (assetBase + fileName.gz). 481 * - In a Zip archive, uncompressed or compressed. 482 * 483 * It can be in a number of different directories and Zip archives. 484 * The search order is: 485 * - [appname] 486 * - locale + vendor 487 * - "default" + vendor 488 * - locale + "default" 489 * - "default + "default" 490 * - "common" 491 * - (same as above) 492 * 493 * To find a particular file, we have to try up to eight paths with 494 * all three forms of data. 495 * 496 * We should probably reject requests for "illegal" filenames, e.g. those 497 * with illegal characters or "../" backward relative paths. 498 */ 499 Asset* AssetManager::open(const char* fileName, AccessMode mode) 500 { 501 AutoMutex _l(mLock); 502 503 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 504 505 506 if (mCacheMode != CACHE_OFF && !mCacheValid) 507 loadFileNameCacheLocked(); 508 509 String8 assetName(kAssetsRoot); 510 assetName.appendPath(fileName); 511 512 /* 513 * For each top-level asset path, search for the asset. 514 */ 515 516 size_t i = mAssetPaths.size(); 517 while (i > 0) { 518 i--; 519 ALOGV("Looking for asset '%s' in '%s'\n", 520 assetName.string(), mAssetPaths.itemAt(i).path.string()); 521 Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i)); 522 if (pAsset != NULL) { 523 return pAsset != kExcludedAsset ? pAsset : NULL; 524 } 525 } 526 527 return NULL; 528 } 529 530 /* 531 * Open a non-asset file as if it were an asset. 532 * 533 * The "fileName" is the partial path starting from the application 534 * name. 535 */ 536 Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode) 537 { 538 AutoMutex _l(mLock); 539 540 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 541 542 543 if (mCacheMode != CACHE_OFF && !mCacheValid) 544 loadFileNameCacheLocked(); 545 546 /* 547 * For each top-level asset path, search for the asset. 548 */ 549 550 size_t i = mAssetPaths.size(); 551 while (i > 0) { 552 i--; 553 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string()); 554 Asset* pAsset = openNonAssetInPathLocked( 555 fileName, mode, mAssetPaths.itemAt(i)); 556 if (pAsset != NULL) { 557 return pAsset != kExcludedAsset ? pAsset : NULL; 558 } 559 } 560 561 return NULL; 562 } 563 564 Asset* AssetManager::openNonAsset(void* cookie, const char* fileName, AccessMode mode) 565 { 566 const size_t which = ((size_t)cookie)-1; 567 568 AutoMutex _l(mLock); 569 570 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 571 572 573 if (mCacheMode != CACHE_OFF && !mCacheValid) 574 loadFileNameCacheLocked(); 575 576 if (which < mAssetPaths.size()) { 577 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, 578 mAssetPaths.itemAt(which).path.string()); 579 Asset* pAsset = openNonAssetInPathLocked( 580 fileName, mode, mAssetPaths.itemAt(which)); 581 if (pAsset != NULL) { 582 return pAsset != kExcludedAsset ? pAsset : NULL; 583 } 584 } 585 586 return NULL; 587 } 588 589 /* 590 * Get the type of a file in the asset namespace. 591 * 592 * This currently only works for regular files. All others (including 593 * directories) will return kFileTypeNonexistent. 594 */ 595 FileType AssetManager::getFileType(const char* fileName) 596 { 597 Asset* pAsset = NULL; 598 599 /* 600 * Open the asset. This is less efficient than simply finding the 601 * file, but it's not too bad (we don't uncompress or mmap data until 602 * the first read() call). 603 */ 604 pAsset = open(fileName, Asset::ACCESS_STREAMING); 605 delete pAsset; 606 607 if (pAsset == NULL) 608 return kFileTypeNonexistent; 609 else 610 return kFileTypeRegular; 611 } 612 613 const ResTable* AssetManager::getResTable(bool required) const 614 { 615 ResTable* rt = mResources; 616 if (rt) { 617 return rt; 618 } 619 620 // Iterate through all asset packages, collecting resources from each. 621 622 AutoMutex _l(mLock); 623 624 if (mResources != NULL) { 625 return mResources; 626 } 627 628 if (required) { 629 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 630 } 631 632 if (mCacheMode != CACHE_OFF && !mCacheValid) 633 const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); 634 635 const size_t N = mAssetPaths.size(); 636 for (size_t i=0; i<N; i++) { 637 Asset* ass = NULL; 638 ResTable* sharedRes = NULL; 639 bool shared = true; 640 const asset_path& ap = mAssetPaths.itemAt(i); 641 Asset* idmap = openIdmapLocked(ap); 642 ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); 643 if (ap.type != kFileTypeDirectory) { 644 if (i == 0) { 645 // The first item is typically the framework resources, 646 // which we want to avoid parsing every time. 647 sharedRes = const_cast<AssetManager*>(this)-> 648 mZipSet.getZipResourceTable(ap.path); 649 } 650 if (sharedRes == NULL) { 651 ass = const_cast<AssetManager*>(this)-> 652 mZipSet.getZipResourceTableAsset(ap.path); 653 if (ass == NULL) { 654 ALOGV("loading resource table %s\n", ap.path.string()); 655 ass = const_cast<AssetManager*>(this)-> 656 openNonAssetInPathLocked("resources.arsc", 657 Asset::ACCESS_BUFFER, 658 ap); 659 if (ass != NULL && ass != kExcludedAsset) { 660 ass = const_cast<AssetManager*>(this)-> 661 mZipSet.setZipResourceTableAsset(ap.path, ass); 662 } 663 } 664 665 if (i == 0 && ass != NULL) { 666 // If this is the first resource table in the asset 667 // manager, then we are going to cache it so that we 668 // can quickly copy it out for others. 669 ALOGV("Creating shared resources for %s", ap.path.string()); 670 sharedRes = new ResTable(); 671 sharedRes->add(ass, (void*)(i+1), false, idmap); 672 sharedRes = const_cast<AssetManager*>(this)-> 673 mZipSet.setZipResourceTable(ap.path, sharedRes); 674 } 675 } 676 } else { 677 ALOGV("loading resource table %s\n", ap.path.string()); 678 Asset* ass = const_cast<AssetManager*>(this)-> 679 openNonAssetInPathLocked("resources.arsc", 680 Asset::ACCESS_BUFFER, 681 ap); 682 shared = false; 683 } 684 if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { 685 if (rt == NULL) { 686 mResources = rt = new ResTable(); 687 updateResourceParamsLocked(); 688 } 689 ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); 690 if (sharedRes != NULL) { 691 ALOGV("Copying existing resources for %s", ap.path.string()); 692 rt->add(sharedRes); 693 } else { 694 ALOGV("Parsing resources for %s", ap.path.string()); 695 rt->add(ass, (void*)(i+1), !shared, idmap); 696 } 697 698 if (!shared) { 699 delete ass; 700 } 701 } 702 if (idmap != NULL) { 703 delete idmap; 704 } 705 } 706 707 if (required && !rt) ALOGW("Unable to find resources file resources.arsc"); 708 if (!rt) { 709 mResources = rt = new ResTable(); 710 } 711 return rt; 712 } 713 714 void AssetManager::updateResourceParamsLocked() const 715 { 716 ResTable* res = mResources; 717 if (!res) { 718 return; 719 } 720 721 size_t llen = mLocale ? strlen(mLocale) : 0; 722 mConfig->language[0] = 0; 723 mConfig->language[1] = 0; 724 mConfig->country[0] = 0; 725 mConfig->country[1] = 0; 726 if (llen >= 2) { 727 mConfig->language[0] = mLocale[0]; 728 mConfig->language[1] = mLocale[1]; 729 } 730 if (llen >= 5) { 731 mConfig->country[0] = mLocale[3]; 732 mConfig->country[1] = mLocale[4]; 733 } 734 mConfig->size = sizeof(*mConfig); 735 736 res->setParameters(mConfig); 737 } 738 739 Asset* AssetManager::openIdmapLocked(const struct asset_path& ap) const 740 { 741 Asset* ass = NULL; 742 if (ap.idmap.size() != 0) { 743 ass = const_cast<AssetManager*>(this)-> 744 openAssetFromFileLocked(ap.idmap, Asset::ACCESS_BUFFER); 745 if (ass) { 746 ALOGV("loading idmap %s\n", ap.idmap.string()); 747 } else { 748 ALOGW("failed to load idmap %s\n", ap.idmap.string()); 749 } 750 } 751 return ass; 752 } 753 754 const ResTable& AssetManager::getResources(bool required) const 755 { 756 const ResTable* rt = getResTable(required); 757 return *rt; 758 } 759 760 bool AssetManager::isUpToDate() 761 { 762 AutoMutex _l(mLock); 763 return mZipSet.isUpToDate(); 764 } 765 766 void AssetManager::getLocales(Vector<String8>* locales) const 767 { 768 ResTable* res = mResources; 769 if (res != NULL) { 770 res->getLocales(locales); 771 } 772 } 773 774 /* 775 * Open a non-asset file as if it were an asset, searching for it in the 776 * specified app. 777 * 778 * Pass in a NULL values for "appName" if the common app directory should 779 * be used. 780 */ 781 Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode, 782 const asset_path& ap) 783 { 784 Asset* pAsset = NULL; 785 786 /* look at the filesystem on disk */ 787 if (ap.type == kFileTypeDirectory) { 788 String8 path(ap.path); 789 path.appendPath(fileName); 790 791 pAsset = openAssetFromFileLocked(path, mode); 792 793 if (pAsset == NULL) { 794 /* try again, this time with ".gz" */ 795 path.append(".gz"); 796 pAsset = openAssetFromFileLocked(path, mode); 797 } 798 799 if (pAsset != NULL) { 800 //printf("FOUND NA '%s' on disk\n", fileName); 801 pAsset->setAssetSource(path); 802 } 803 804 /* look inside the zip file */ 805 } else { 806 String8 path(fileName); 807 808 /* check the appropriate Zip file */ 809 ZipFileRO* pZip; 810 ZipEntryRO entry; 811 812 pZip = getZipFileLocked(ap); 813 if (pZip != NULL) { 814 //printf("GOT zip, checking NA '%s'\n", (const char*) path); 815 entry = pZip->findEntryByName(path.string()); 816 if (entry != NULL) { 817 //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon); 818 pAsset = openAssetFromZipLocked(pZip, entry, mode, path); 819 } 820 } 821 822 if (pAsset != NULL) { 823 /* create a "source" name, for debug/display */ 824 pAsset->setAssetSource( 825 createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""), 826 String8(fileName))); 827 } 828 } 829 830 return pAsset; 831 } 832 833 /* 834 * Open an asset, searching for it in the directory hierarchy for the 835 * specified app. 836 * 837 * Pass in a NULL values for "appName" if the common app directory should 838 * be used. 839 */ 840 Asset* AssetManager::openInPathLocked(const char* fileName, AccessMode mode, 841 const asset_path& ap) 842 { 843 Asset* pAsset = NULL; 844 845 /* 846 * Try various combinations of locale and vendor. 847 */ 848 if (mLocale != NULL && mVendor != NULL) 849 pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, mVendor); 850 if (pAsset == NULL && mVendor != NULL) 851 pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, mVendor); 852 if (pAsset == NULL && mLocale != NULL) 853 pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, NULL); 854 if (pAsset == NULL) 855 pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, NULL); 856 857 return pAsset; 858 } 859 860 /* 861 * Open an asset, searching for it in the directory hierarchy for the 862 * specified locale and vendor. 863 * 864 * We also search in "app.jar". 865 * 866 * Pass in NULL values for "appName", "locale", and "vendor" if the 867 * defaults should be used. 868 */ 869 Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode mode, 870 const asset_path& ap, const char* locale, const char* vendor) 871 { 872 Asset* pAsset = NULL; 873 874 if (ap.type == kFileTypeDirectory) { 875 if (mCacheMode == CACHE_OFF) { 876 /* look at the filesystem on disk */ 877 String8 path(createPathNameLocked(ap, locale, vendor)); 878 path.appendPath(fileName); 879 880 String8 excludeName(path); 881 excludeName.append(kExcludeExtension); 882 if (::getFileType(excludeName.string()) != kFileTypeNonexistent) { 883 /* say no more */ 884 //printf("+++ excluding '%s'\n", (const char*) excludeName); 885 return kExcludedAsset; 886 } 887 888 pAsset = openAssetFromFileLocked(path, mode); 889 890 if (pAsset == NULL) { 891 /* try again, this time with ".gz" */ 892 path.append(".gz"); 893 pAsset = openAssetFromFileLocked(path, mode); 894 } 895 896 if (pAsset != NULL) 897 pAsset->setAssetSource(path); 898 } else { 899 /* find in cache */ 900 String8 path(createPathNameLocked(ap, locale, vendor)); 901 path.appendPath(fileName); 902 903 AssetDir::FileInfo tmpInfo; 904 bool found = false; 905 906 String8 excludeName(path); 907 excludeName.append(kExcludeExtension); 908 909 if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) { 910 /* go no farther */ 911 //printf("+++ Excluding '%s'\n", (const char*) excludeName); 912 return kExcludedAsset; 913 } 914 915 /* 916 * File compression extensions (".gz") don't get stored in the 917 * name cache, so we have to try both here. 918 */ 919 if (mCache.indexOf(path) != NAME_NOT_FOUND) { 920 found = true; 921 pAsset = openAssetFromFileLocked(path, mode); 922 if (pAsset == NULL) { 923 /* try again, this time with ".gz" */ 924 path.append(".gz"); 925 pAsset = openAssetFromFileLocked(path, mode); 926 } 927 } 928 929 if (pAsset != NULL) 930 pAsset->setAssetSource(path); 931 932 /* 933 * Don't continue the search into the Zip files. Our cached info 934 * said it was a file on disk; to be consistent with openDir() 935 * we want to return the loose asset. If the cached file gets 936 * removed, we fail. 937 * 938 * The alternative is to update our cache when files get deleted, 939 * or make some sort of "best effort" promise, but for now I'm 940 * taking the hard line. 941 */ 942 if (found) { 943 if (pAsset == NULL) 944 ALOGD("Expected file not found: '%s'\n", path.string()); 945 return pAsset; 946 } 947 } 948 } 949 950 /* 951 * Either it wasn't found on disk or on the cached view of the disk. 952 * Dig through the currently-opened set of Zip files. If caching 953 * is disabled, the Zip file may get reopened. 954 */ 955 if (pAsset == NULL && ap.type == kFileTypeRegular) { 956 String8 path; 957 958 path.appendPath((locale != NULL) ? locale : kDefaultLocale); 959 path.appendPath((vendor != NULL) ? vendor : kDefaultVendor); 960 path.appendPath(fileName); 961 962 /* check the appropriate Zip file */ 963 ZipFileRO* pZip; 964 ZipEntryRO entry; 965 966 pZip = getZipFileLocked(ap); 967 if (pZip != NULL) { 968 //printf("GOT zip, checking '%s'\n", (const char*) path); 969 entry = pZip->findEntryByName(path.string()); 970 if (entry != NULL) { 971 //printf("FOUND in Zip file for %s/%s-%s\n", 972 // appName, locale, vendor); 973 pAsset = openAssetFromZipLocked(pZip, entry, mode, path); 974 } 975 } 976 977 if (pAsset != NULL) { 978 /* create a "source" name, for debug/display */ 979 pAsset->setAssetSource(createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), 980 String8(""), String8(fileName))); 981 } 982 } 983 984 return pAsset; 985 } 986 987 /* 988 * Create a "source name" for a file from a Zip archive. 989 */ 990 String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName, 991 const String8& dirName, const String8& fileName) 992 { 993 String8 sourceName("zip:"); 994 sourceName.append(zipFileName); 995 sourceName.append(":"); 996 if (dirName.length() > 0) { 997 sourceName.appendPath(dirName); 998 } 999 sourceName.appendPath(fileName); 1000 return sourceName; 1001 } 1002 1003 /* 1004 * Create a path to a loose asset (asset-base/app/locale/vendor). 1005 */ 1006 String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* locale, 1007 const char* vendor) 1008 { 1009 String8 path(ap.path); 1010 path.appendPath((locale != NULL) ? locale : kDefaultLocale); 1011 path.appendPath((vendor != NULL) ? vendor : kDefaultVendor); 1012 return path; 1013 } 1014 1015 /* 1016 * Create a path to a loose asset (asset-base/app/rootDir). 1017 */ 1018 String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir) 1019 { 1020 String8 path(ap.path); 1021 if (rootDir != NULL) path.appendPath(rootDir); 1022 return path; 1023 } 1024 1025 /* 1026 * Return a pointer to one of our open Zip archives. Returns NULL if no 1027 * matching Zip file exists. 1028 * 1029 * Right now we have 2 possible Zip files (1 each in app/"common"). 1030 * 1031 * If caching is set to CACHE_OFF, to get the expected behavior we 1032 * need to reopen the Zip file on every request. That would be silly 1033 * and expensive, so instead we just check the file modification date. 1034 * 1035 * Pass in NULL values for "appName", "locale", and "vendor" if the 1036 * generics should be used. 1037 */ 1038 ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap) 1039 { 1040 ALOGV("getZipFileLocked() in %p\n", this); 1041 1042 return mZipSet.getZip(ap.path); 1043 } 1044 1045 /* 1046 * Try to open an asset from a file on disk. 1047 * 1048 * If the file is compressed with gzip, we seek to the start of the 1049 * deflated data and pass that in (just like we would for a Zip archive). 1050 * 1051 * For uncompressed data, we may already have an mmap()ed version sitting 1052 * around. If so, we want to hand that to the Asset instead. 1053 * 1054 * This returns NULL if the file doesn't exist, couldn't be opened, or 1055 * claims to be a ".gz" but isn't. 1056 */ 1057 Asset* AssetManager::openAssetFromFileLocked(const String8& pathName, 1058 AccessMode mode) 1059 { 1060 Asset* pAsset = NULL; 1061 1062 if (strcasecmp(pathName.getPathExtension().string(), ".gz") == 0) { 1063 //printf("TRYING '%s'\n", (const char*) pathName); 1064 pAsset = Asset::createFromCompressedFile(pathName.string(), mode); 1065 } else { 1066 //printf("TRYING '%s'\n", (const char*) pathName); 1067 pAsset = Asset::createFromFile(pathName.string(), mode); 1068 } 1069 1070 return pAsset; 1071 } 1072 1073 /* 1074 * Given an entry in a Zip archive, create a new Asset object. 1075 * 1076 * If the entry is uncompressed, we may want to create or share a 1077 * slice of shared memory. 1078 */ 1079 Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile, 1080 const ZipEntryRO entry, AccessMode mode, const String8& entryName) 1081 { 1082 Asset* pAsset = NULL; 1083 1084 // TODO: look for previously-created shared memory slice? 1085 int method; 1086 size_t uncompressedLen; 1087 1088 //printf("USING Zip '%s'\n", pEntry->getFileName()); 1089 1090 //pZipFile->getEntryInfo(entry, &method, &uncompressedLen, &compressedLen, 1091 // &offset); 1092 if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL, 1093 NULL, NULL)) 1094 { 1095 ALOGW("getEntryInfo failed\n"); 1096 return NULL; 1097 } 1098 1099 FileMap* dataMap = pZipFile->createEntryFileMap(entry); 1100 if (dataMap == NULL) { 1101 ALOGW("create map from entry failed\n"); 1102 return NULL; 1103 } 1104 1105 if (method == ZipFileRO::kCompressStored) { 1106 pAsset = Asset::createFromUncompressedMap(dataMap, mode); 1107 ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(), 1108 dataMap->getFileName(), mode, pAsset); 1109 } else { 1110 pAsset = Asset::createFromCompressedMap(dataMap, method, 1111 uncompressedLen, mode); 1112 ALOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(), 1113 dataMap->getFileName(), mode, pAsset); 1114 } 1115 if (pAsset == NULL) { 1116 /* unexpected */ 1117 ALOGW("create from segment failed\n"); 1118 } 1119 1120 return pAsset; 1121 } 1122 1123 1124 1125 /* 1126 * Open a directory in the asset namespace. 1127 * 1128 * An "asset directory" is simply the combination of all files in all 1129 * locations, with ".gz" stripped for loose files. With app, locale, and 1130 * vendor defined, we have 8 directories and 2 Zip archives to scan. 1131 * 1132 * Pass in "" for the root dir. 1133 */ 1134 AssetDir* AssetManager::openDir(const char* dirName) 1135 { 1136 AutoMutex _l(mLock); 1137 1138 AssetDir* pDir = NULL; 1139 SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL; 1140 1141 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 1142 assert(dirName != NULL); 1143 1144 //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase); 1145 1146 if (mCacheMode != CACHE_OFF && !mCacheValid) 1147 loadFileNameCacheLocked(); 1148 1149 pDir = new AssetDir; 1150 1151 /* 1152 * Scan the various directories, merging what we find into a single 1153 * vector. We want to scan them in reverse priority order so that 1154 * the ".EXCLUDE" processing works correctly. Also, if we decide we 1155 * want to remember where the file is coming from, we'll get the right 1156 * version. 1157 * 1158 * We start with Zip archives, then do loose files. 1159 */ 1160 pMergedInfo = new SortedVector<AssetDir::FileInfo>; 1161 1162 size_t i = mAssetPaths.size(); 1163 while (i > 0) { 1164 i--; 1165 const asset_path& ap = mAssetPaths.itemAt(i); 1166 if (ap.type == kFileTypeRegular) { 1167 ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); 1168 scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName); 1169 } else { 1170 ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); 1171 scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName); 1172 } 1173 } 1174 1175 #if 0 1176 printf("FILE LIST:\n"); 1177 for (i = 0; i < (size_t) pMergedInfo->size(); i++) { 1178 printf(" %d: (%d) '%s'\n", i, 1179 pMergedInfo->itemAt(i).getFileType(), 1180 (const char*) pMergedInfo->itemAt(i).getFileName()); 1181 } 1182 #endif 1183 1184 pDir->setFileList(pMergedInfo); 1185 return pDir; 1186 } 1187 1188 /* 1189 * Open a directory in the non-asset namespace. 1190 * 1191 * An "asset directory" is simply the combination of all files in all 1192 * locations, with ".gz" stripped for loose files. With app, locale, and 1193 * vendor defined, we have 8 directories and 2 Zip archives to scan. 1194 * 1195 * Pass in "" for the root dir. 1196 */ 1197 AssetDir* AssetManager::openNonAssetDir(void* cookie, const char* dirName) 1198 { 1199 AutoMutex _l(mLock); 1200 1201 AssetDir* pDir = NULL; 1202 SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL; 1203 1204 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 1205 assert(dirName != NULL); 1206 1207 //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase); 1208 1209 if (mCacheMode != CACHE_OFF && !mCacheValid) 1210 loadFileNameCacheLocked(); 1211 1212 pDir = new AssetDir; 1213 1214 pMergedInfo = new SortedVector<AssetDir::FileInfo>; 1215 1216 const size_t which = ((size_t)cookie)-1; 1217 1218 if (which < mAssetPaths.size()) { 1219 const asset_path& ap = mAssetPaths.itemAt(which); 1220 if (ap.type == kFileTypeRegular) { 1221 ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); 1222 scanAndMergeZipLocked(pMergedInfo, ap, NULL, dirName); 1223 } else { 1224 ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); 1225 scanAndMergeDirLocked(pMergedInfo, ap, NULL, dirName); 1226 } 1227 } 1228 1229 #if 0 1230 printf("FILE LIST:\n"); 1231 for (i = 0; i < (size_t) pMergedInfo->size(); i++) { 1232 printf(" %d: (%d) '%s'\n", i, 1233 pMergedInfo->itemAt(i).getFileType(), 1234 (const char*) pMergedInfo->itemAt(i).getFileName()); 1235 } 1236 #endif 1237 1238 pDir->setFileList(pMergedInfo); 1239 return pDir; 1240 } 1241 1242 /* 1243 * Scan the contents of the specified directory and merge them into the 1244 * "pMergedInfo" vector, removing previous entries if we find "exclude" 1245 * directives. 1246 * 1247 * Returns "false" if we found nothing to contribute. 1248 */ 1249 bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, 1250 const asset_path& ap, const char* rootDir, const char* dirName) 1251 { 1252 SortedVector<AssetDir::FileInfo>* pContents; 1253 String8 path; 1254 1255 assert(pMergedInfo != NULL); 1256 1257 //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName); 1258 1259 if (mCacheValid) { 1260 int i, start, count; 1261 1262 pContents = new SortedVector<AssetDir::FileInfo>; 1263 1264 /* 1265 * Get the basic partial path and find it in the cache. That's 1266 * the start point for the search. 1267 */ 1268 path = createPathNameLocked(ap, rootDir); 1269 if (dirName[0] != '\0') 1270 path.appendPath(dirName); 1271 1272 start = mCache.indexOf(path); 1273 if (start == NAME_NOT_FOUND) { 1274 //printf("+++ not found in cache: dir '%s'\n", (const char*) path); 1275 delete pContents; 1276 return false; 1277 } 1278 1279 /* 1280 * The match string looks like "common/default/default/foo/bar/". 1281 * The '/' on the end ensures that we don't match on the directory 1282 * itself or on ".../foo/barfy/". 1283 */ 1284 path.append("/"); 1285 1286 count = mCache.size(); 1287 1288 /* 1289 * Pick out the stuff in the current dir by examining the pathname. 1290 * It needs to match the partial pathname prefix, and not have a '/' 1291 * (fssep) anywhere after the prefix. 1292 */ 1293 for (i = start+1; i < count; i++) { 1294 if (mCache[i].getFileName().length() > path.length() && 1295 strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0) 1296 { 1297 const char* name = mCache[i].getFileName().string(); 1298 // XXX THIS IS BROKEN! Looks like we need to store the full 1299 // path prefix separately from the file path. 1300 if (strchr(name + path.length(), '/') == NULL) { 1301 /* grab it, reducing path to just the filename component */ 1302 AssetDir::FileInfo tmp = mCache[i]; 1303 tmp.setFileName(tmp.getFileName().getPathLeaf()); 1304 pContents->add(tmp); 1305 } 1306 } else { 1307 /* no longer in the dir or its subdirs */ 1308 break; 1309 } 1310 1311 } 1312 } else { 1313 path = createPathNameLocked(ap, rootDir); 1314 if (dirName[0] != '\0') 1315 path.appendPath(dirName); 1316 pContents = scanDirLocked(path); 1317 if (pContents == NULL) 1318 return false; 1319 } 1320 1321 // if we wanted to do an incremental cache fill, we would do it here 1322 1323 /* 1324 * Process "exclude" directives. If we find a filename that ends with 1325 * ".EXCLUDE", we look for a matching entry in the "merged" set, and 1326 * remove it if we find it. We also delete the "exclude" entry. 1327 */ 1328 int i, count, exclExtLen; 1329 1330 count = pContents->size(); 1331 exclExtLen = strlen(kExcludeExtension); 1332 for (i = 0; i < count; i++) { 1333 const char* name; 1334 int nameLen; 1335 1336 name = pContents->itemAt(i).getFileName().string(); 1337 nameLen = strlen(name); 1338 if (nameLen > exclExtLen && 1339 strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0) 1340 { 1341 String8 match(name, nameLen - exclExtLen); 1342 int matchIdx; 1343 1344 matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match); 1345 if (matchIdx > 0) { 1346 ALOGV("Excluding '%s' [%s]\n", 1347 pMergedInfo->itemAt(matchIdx).getFileName().string(), 1348 pMergedInfo->itemAt(matchIdx).getSourceName().string()); 1349 pMergedInfo->removeAt(matchIdx); 1350 } else { 1351 //printf("+++ no match on '%s'\n", (const char*) match); 1352 } 1353 1354 ALOGD("HEY: size=%d removing %d\n", (int)pContents->size(), i); 1355 pContents->removeAt(i); 1356 i--; // adjust "for" loop 1357 count--; // and loop limit 1358 } 1359 } 1360 1361 mergeInfoLocked(pMergedInfo, pContents); 1362 1363 delete pContents; 1364 1365 return true; 1366 } 1367 1368 /* 1369 * Scan the contents of the specified directory, and stuff what we find 1370 * into a newly-allocated vector. 1371 * 1372 * Files ending in ".gz" will have their extensions removed. 1373 * 1374 * We should probably think about skipping files with "illegal" names, 1375 * e.g. illegal characters (/\:) or excessive length. 1376 * 1377 * Returns NULL if the specified directory doesn't exist. 1378 */ 1379 SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& path) 1380 { 1381 SortedVector<AssetDir::FileInfo>* pContents = NULL; 1382 DIR* dir; 1383 struct dirent* entry; 1384 FileType fileType; 1385 1386 ALOGV("Scanning dir '%s'\n", path.string()); 1387 1388 dir = opendir(path.string()); 1389 if (dir == NULL) 1390 return NULL; 1391 1392 pContents = new SortedVector<AssetDir::FileInfo>; 1393 1394 while (1) { 1395 entry = readdir(dir); 1396 if (entry == NULL) 1397 break; 1398 1399 if (strcmp(entry->d_name, ".") == 0 || 1400 strcmp(entry->d_name, "..") == 0) 1401 continue; 1402 1403 #ifdef _DIRENT_HAVE_D_TYPE 1404 if (entry->d_type == DT_REG) 1405 fileType = kFileTypeRegular; 1406 else if (entry->d_type == DT_DIR) 1407 fileType = kFileTypeDirectory; 1408 else 1409 fileType = kFileTypeUnknown; 1410 #else 1411 // stat the file 1412 fileType = ::getFileType(path.appendPathCopy(entry->d_name).string()); 1413 #endif 1414 1415 if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory) 1416 continue; 1417 1418 AssetDir::FileInfo info; 1419 info.set(String8(entry->d_name), fileType); 1420 if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0) 1421 info.setFileName(info.getFileName().getBasePath()); 1422 info.setSourceName(path.appendPathCopy(info.getFileName())); 1423 pContents->add(info); 1424 } 1425 1426 closedir(dir); 1427 return pContents; 1428 } 1429 1430 /* 1431 * Scan the contents out of the specified Zip archive, and merge what we 1432 * find into "pMergedInfo". If the Zip archive in question doesn't exist, 1433 * we return immediately. 1434 * 1435 * Returns "false" if we found nothing to contribute. 1436 */ 1437 bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, 1438 const asset_path& ap, const char* rootDir, const char* baseDirName) 1439 { 1440 ZipFileRO* pZip; 1441 Vector<String8> dirs; 1442 AssetDir::FileInfo info; 1443 SortedVector<AssetDir::FileInfo> contents; 1444 String8 sourceName, zipName, dirName; 1445 1446 pZip = mZipSet.getZip(ap.path); 1447 if (pZip == NULL) { 1448 ALOGW("Failure opening zip %s\n", ap.path.string()); 1449 return false; 1450 } 1451 1452 zipName = ZipSet::getPathName(ap.path.string()); 1453 1454 /* convert "sounds" to "rootDir/sounds" */ 1455 if (rootDir != NULL) dirName = rootDir; 1456 dirName.appendPath(baseDirName); 1457 1458 /* 1459 * Scan through the list of files, looking for a match. The files in 1460 * the Zip table of contents are not in sorted order, so we have to 1461 * process the entire list. We're looking for a string that begins 1462 * with the characters in "dirName", is followed by a '/', and has no 1463 * subsequent '/' in the stuff that follows. 1464 * 1465 * What makes this especially fun is that directories are not stored 1466 * explicitly in Zip archives, so we have to infer them from context. 1467 * When we see "sounds/foo.wav" we have to leave a note to ourselves 1468 * to insert a directory called "sounds" into the list. We store 1469 * these in temporary vector so that we only return each one once. 1470 * 1471 * Name comparisons are case-sensitive to match UNIX filesystem 1472 * semantics. 1473 */ 1474 int dirNameLen = dirName.length(); 1475 for (int i = 0; i < pZip->getNumEntries(); i++) { 1476 ZipEntryRO entry; 1477 char nameBuf[256]; 1478 1479 entry = pZip->findEntryByIndex(i); 1480 if (pZip->getEntryFileName(entry, nameBuf, sizeof(nameBuf)) != 0) { 1481 // TODO: fix this if we expect to have long names 1482 ALOGE("ARGH: name too long?\n"); 1483 continue; 1484 } 1485 //printf("Comparing %s in %s?\n", nameBuf, dirName.string()); 1486 if (dirNameLen == 0 || 1487 (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 && 1488 nameBuf[dirNameLen] == '/')) 1489 { 1490 const char* cp; 1491 const char* nextSlash; 1492 1493 cp = nameBuf + dirNameLen; 1494 if (dirNameLen != 0) 1495 cp++; // advance past the '/' 1496 1497 nextSlash = strchr(cp, '/'); 1498 //xxx this may break if there are bare directory entries 1499 if (nextSlash == NULL) { 1500 /* this is a file in the requested directory */ 1501 1502 info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular); 1503 1504 info.setSourceName( 1505 createZipSourceNameLocked(zipName, dirName, info.getFileName())); 1506 1507 contents.add(info); 1508 //printf("FOUND: file '%s'\n", info.getFileName().string()); 1509 } else { 1510 /* this is a subdir; add it if we don't already have it*/ 1511 String8 subdirName(cp, nextSlash - cp); 1512 size_t j; 1513 size_t N = dirs.size(); 1514 1515 for (j = 0; j < N; j++) { 1516 if (subdirName == dirs[j]) { 1517 break; 1518 } 1519 } 1520 if (j == N) { 1521 dirs.add(subdirName); 1522 } 1523 1524 //printf("FOUND: dir '%s'\n", subdirName.string()); 1525 } 1526 } 1527 } 1528 1529 /* 1530 * Add the set of unique directories. 1531 */ 1532 for (int i = 0; i < (int) dirs.size(); i++) { 1533 info.set(dirs[i], kFileTypeDirectory); 1534 info.setSourceName( 1535 createZipSourceNameLocked(zipName, dirName, info.getFileName())); 1536 contents.add(info); 1537 } 1538 1539 mergeInfoLocked(pMergedInfo, &contents); 1540 1541 return true; 1542 } 1543 1544 1545 /* 1546 * Merge two vectors of FileInfo. 1547 * 1548 * The merged contents will be stuffed into *pMergedInfo. 1549 * 1550 * If an entry for a file exists in both "pMergedInfo" and "pContents", 1551 * we use the newer "pContents" entry. 1552 */ 1553 void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, 1554 const SortedVector<AssetDir::FileInfo>* pContents) 1555 { 1556 /* 1557 * Merge what we found in this directory with what we found in 1558 * other places. 1559 * 1560 * Two basic approaches: 1561 * (1) Create a new array that holds the unique values of the two 1562 * arrays. 1563 * (2) Take the elements from pContents and shove them into pMergedInfo. 1564 * 1565 * Because these are vectors of complex objects, moving elements around 1566 * inside the vector requires constructing new objects and allocating 1567 * storage for members. With approach #1, we're always adding to the 1568 * end, whereas with #2 we could be inserting multiple elements at the 1569 * front of the vector. Approach #1 requires a full copy of the 1570 * contents of pMergedInfo, but approach #2 requires the same copy for 1571 * every insertion at the front of pMergedInfo. 1572 * 1573 * (We should probably use a SortedVector interface that allows us to 1574 * just stuff items in, trusting us to maintain the sort order.) 1575 */ 1576 SortedVector<AssetDir::FileInfo>* pNewSorted; 1577 int mergeMax, contMax; 1578 int mergeIdx, contIdx; 1579 1580 pNewSorted = new SortedVector<AssetDir::FileInfo>; 1581 mergeMax = pMergedInfo->size(); 1582 contMax = pContents->size(); 1583 mergeIdx = contIdx = 0; 1584 1585 while (mergeIdx < mergeMax || contIdx < contMax) { 1586 if (mergeIdx == mergeMax) { 1587 /* hit end of "merge" list, copy rest of "contents" */ 1588 pNewSorted->add(pContents->itemAt(contIdx)); 1589 contIdx++; 1590 } else if (contIdx == contMax) { 1591 /* hit end of "cont" list, copy rest of "merge" */ 1592 pNewSorted->add(pMergedInfo->itemAt(mergeIdx)); 1593 mergeIdx++; 1594 } else if (pMergedInfo->itemAt(mergeIdx) == pContents->itemAt(contIdx)) 1595 { 1596 /* items are identical, add newer and advance both indices */ 1597 pNewSorted->add(pContents->itemAt(contIdx)); 1598 mergeIdx++; 1599 contIdx++; 1600 } else if (pMergedInfo->itemAt(mergeIdx) < pContents->itemAt(contIdx)) 1601 { 1602 /* "merge" is lower, add that one */ 1603 pNewSorted->add(pMergedInfo->itemAt(mergeIdx)); 1604 mergeIdx++; 1605 } else { 1606 /* "cont" is lower, add that one */ 1607 assert(pContents->itemAt(contIdx) < pMergedInfo->itemAt(mergeIdx)); 1608 pNewSorted->add(pContents->itemAt(contIdx)); 1609 contIdx++; 1610 } 1611 } 1612 1613 /* 1614 * Overwrite the "merged" list with the new stuff. 1615 */ 1616 *pMergedInfo = *pNewSorted; 1617 delete pNewSorted; 1618 1619 #if 0 // for Vector, rather than SortedVector 1620 int i, j; 1621 for (i = pContents->size() -1; i >= 0; i--) { 1622 bool add = true; 1623 1624 for (j = pMergedInfo->size() -1; j >= 0; j--) { 1625 /* case-sensitive comparisons, to behave like UNIX fs */ 1626 if (strcmp(pContents->itemAt(i).mFileName, 1627 pMergedInfo->itemAt(j).mFileName) == 0) 1628 { 1629 /* match, don't add this entry */ 1630 add = false; 1631 break; 1632 } 1633 } 1634 1635 if (add) 1636 pMergedInfo->add(pContents->itemAt(i)); 1637 } 1638 #endif 1639 } 1640 1641 1642 /* 1643 * Load all files into the file name cache. We want to do this across 1644 * all combinations of { appname, locale, vendor }, performing a recursive 1645 * directory traversal. 1646 * 1647 * This is not the most efficient data structure. Also, gathering the 1648 * information as we needed it (file-by-file or directory-by-directory) 1649 * would be faster. However, on the actual device, 99% of the files will 1650 * live in Zip archives, so this list will be very small. The trouble 1651 * is that we have to check the "loose" files first, so it's important 1652 * that we don't beat the filesystem silly looking for files that aren't 1653 * there. 1654 * 1655 * Note on thread safety: this is the only function that causes updates 1656 * to mCache, and anybody who tries to use it will call here if !mCacheValid, 1657 * so we need to employ a mutex here. 1658 */ 1659 void AssetManager::loadFileNameCacheLocked(void) 1660 { 1661 assert(!mCacheValid); 1662 assert(mCache.size() == 0); 1663 1664 #ifdef DO_TIMINGS // need to link against -lrt for this now 1665 DurationTimer timer; 1666 timer.start(); 1667 #endif 1668 1669 fncScanLocked(&mCache, ""); 1670 1671 #ifdef DO_TIMINGS 1672 timer.stop(); 1673 ALOGD("Cache scan took %.3fms\n", 1674 timer.durationUsecs() / 1000.0); 1675 #endif 1676 1677 #if 0 1678 int i; 1679 printf("CACHED FILE LIST (%d entries):\n", mCache.size()); 1680 for (i = 0; i < (int) mCache.size(); i++) { 1681 printf(" %d: (%d) '%s'\n", i, 1682 mCache.itemAt(i).getFileType(), 1683 (const char*) mCache.itemAt(i).getFileName()); 1684 } 1685 #endif 1686 1687 mCacheValid = true; 1688 } 1689 1690 /* 1691 * Scan up to 8 versions of the specified directory. 1692 */ 1693 void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, 1694 const char* dirName) 1695 { 1696 size_t i = mAssetPaths.size(); 1697 while (i > 0) { 1698 i--; 1699 const asset_path& ap = mAssetPaths.itemAt(i); 1700 fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName); 1701 if (mLocale != NULL) 1702 fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName); 1703 if (mVendor != NULL) 1704 fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName); 1705 if (mLocale != NULL && mVendor != NULL) 1706 fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName); 1707 } 1708 } 1709 1710 /* 1711 * Recursively scan this directory and all subdirs. 1712 * 1713 * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE 1714 * files, and we prepend the extended partial path to the filenames. 1715 */ 1716 bool AssetManager::fncScanAndMergeDirLocked( 1717 SortedVector<AssetDir::FileInfo>* pMergedInfo, 1718 const asset_path& ap, const char* locale, const char* vendor, 1719 const char* dirName) 1720 { 1721 SortedVector<AssetDir::FileInfo>* pContents; 1722 String8 partialPath; 1723 String8 fullPath; 1724 1725 // XXX This is broken -- the filename cache needs to hold the base 1726 // asset path separately from its filename. 1727 1728 partialPath = createPathNameLocked(ap, locale, vendor); 1729 if (dirName[0] != '\0') { 1730 partialPath.appendPath(dirName); 1731 } 1732 1733 fullPath = partialPath; 1734 pContents = scanDirLocked(fullPath); 1735 if (pContents == NULL) { 1736 return false; // directory did not exist 1737 } 1738 1739 /* 1740 * Scan all subdirectories of the current dir, merging what we find 1741 * into "pMergedInfo". 1742 */ 1743 for (int i = 0; i < (int) pContents->size(); i++) { 1744 if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) { 1745 String8 subdir(dirName); 1746 subdir.appendPath(pContents->itemAt(i).getFileName()); 1747 1748 fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string()); 1749 } 1750 } 1751 1752 /* 1753 * To be consistent, we want entries for the root directory. If 1754 * we're the root, add one now. 1755 */ 1756 if (dirName[0] == '\0') { 1757 AssetDir::FileInfo tmpInfo; 1758 1759 tmpInfo.set(String8(""), kFileTypeDirectory); 1760 tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor)); 1761 pContents->add(tmpInfo); 1762 } 1763 1764 /* 1765 * We want to prepend the extended partial path to every entry in 1766 * "pContents". It's the same value for each entry, so this will 1767 * not change the sorting order of the vector contents. 1768 */ 1769 for (int i = 0; i < (int) pContents->size(); i++) { 1770 const AssetDir::FileInfo& info = pContents->itemAt(i); 1771 pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName())); 1772 } 1773 1774 mergeInfoLocked(pMergedInfo, pContents); 1775 return true; 1776 } 1777 1778 /* 1779 * Trash the cache. 1780 */ 1781 void AssetManager::purgeFileNameCacheLocked(void) 1782 { 1783 mCacheValid = false; 1784 mCache.clear(); 1785 } 1786 1787 /* 1788 * =========================================================================== 1789 * AssetManager::SharedZip 1790 * =========================================================================== 1791 */ 1792 1793 1794 Mutex AssetManager::SharedZip::gLock; 1795 DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen; 1796 1797 AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen) 1798 : mPath(path), mZipFile(NULL), mModWhen(modWhen), 1799 mResourceTableAsset(NULL), mResourceTable(NULL) 1800 { 1801 //ALOGI("Creating SharedZip %p %s\n", this, (const char*)mPath); 1802 mZipFile = new ZipFileRO; 1803 ALOGV("+++ opening zip '%s'\n", mPath.string()); 1804 if (mZipFile->open(mPath.string()) != NO_ERROR) { 1805 ALOGD("failed to open Zip archive '%s'\n", mPath.string()); 1806 delete mZipFile; 1807 mZipFile = NULL; 1808 } 1809 } 1810 1811 sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path) 1812 { 1813 AutoMutex _l(gLock); 1814 time_t modWhen = getFileModDate(path); 1815 sp<SharedZip> zip = gOpen.valueFor(path).promote(); 1816 if (zip != NULL && zip->mModWhen == modWhen) { 1817 return zip; 1818 } 1819 zip = new SharedZip(path, modWhen); 1820 gOpen.add(path, zip); 1821 return zip; 1822 1823 } 1824 1825 ZipFileRO* AssetManager::SharedZip::getZip() 1826 { 1827 return mZipFile; 1828 } 1829 1830 Asset* AssetManager::SharedZip::getResourceTableAsset() 1831 { 1832 ALOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset); 1833 return mResourceTableAsset; 1834 } 1835 1836 Asset* AssetManager::SharedZip::setResourceTableAsset(Asset* asset) 1837 { 1838 { 1839 AutoMutex _l(gLock); 1840 if (mResourceTableAsset == NULL) { 1841 mResourceTableAsset = asset; 1842 // This is not thread safe the first time it is called, so 1843 // do it here with the global lock held. 1844 asset->getBuffer(true); 1845 return asset; 1846 } 1847 } 1848 delete asset; 1849 return mResourceTableAsset; 1850 } 1851 1852 ResTable* AssetManager::SharedZip::getResourceTable() 1853 { 1854 ALOGV("Getting from SharedZip %p resource table %p\n", this, mResourceTable); 1855 return mResourceTable; 1856 } 1857 1858 ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res) 1859 { 1860 { 1861 AutoMutex _l(gLock); 1862 if (mResourceTable == NULL) { 1863 mResourceTable = res; 1864 return res; 1865 } 1866 } 1867 delete res; 1868 return mResourceTable; 1869 } 1870 1871 bool AssetManager::SharedZip::isUpToDate() 1872 { 1873 time_t modWhen = getFileModDate(mPath.string()); 1874 return mModWhen == modWhen; 1875 } 1876 1877 AssetManager::SharedZip::~SharedZip() 1878 { 1879 //ALOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath); 1880 if (mResourceTable != NULL) { 1881 delete mResourceTable; 1882 } 1883 if (mResourceTableAsset != NULL) { 1884 delete mResourceTableAsset; 1885 } 1886 if (mZipFile != NULL) { 1887 delete mZipFile; 1888 ALOGV("Closed '%s'\n", mPath.string()); 1889 } 1890 } 1891 1892 /* 1893 * =========================================================================== 1894 * AssetManager::ZipSet 1895 * =========================================================================== 1896 */ 1897 1898 /* 1899 * Constructor. 1900 */ 1901 AssetManager::ZipSet::ZipSet(void) 1902 { 1903 } 1904 1905 /* 1906 * Destructor. Close any open archives. 1907 */ 1908 AssetManager::ZipSet::~ZipSet(void) 1909 { 1910 size_t N = mZipFile.size(); 1911 for (size_t i = 0; i < N; i++) 1912 closeZip(i); 1913 } 1914 1915 /* 1916 * Close a Zip file and reset the entry. 1917 */ 1918 void AssetManager::ZipSet::closeZip(int idx) 1919 { 1920 mZipFile.editItemAt(idx) = NULL; 1921 } 1922 1923 1924 /* 1925 * Retrieve the appropriate Zip file from the set. 1926 */ 1927 ZipFileRO* AssetManager::ZipSet::getZip(const String8& path) 1928 { 1929 int idx = getIndex(path); 1930 sp<SharedZip> zip = mZipFile[idx]; 1931 if (zip == NULL) { 1932 zip = SharedZip::get(path); 1933 mZipFile.editItemAt(idx) = zip; 1934 } 1935 return zip->getZip(); 1936 } 1937 1938 Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path) 1939 { 1940 int idx = getIndex(path); 1941 sp<SharedZip> zip = mZipFile[idx]; 1942 if (zip == NULL) { 1943 zip = SharedZip::get(path); 1944 mZipFile.editItemAt(idx) = zip; 1945 } 1946 return zip->getResourceTableAsset(); 1947 } 1948 1949 Asset* AssetManager::ZipSet::setZipResourceTableAsset(const String8& path, 1950 Asset* asset) 1951 { 1952 int idx = getIndex(path); 1953 sp<SharedZip> zip = mZipFile[idx]; 1954 // doesn't make sense to call before previously accessing. 1955 return zip->setResourceTableAsset(asset); 1956 } 1957 1958 ResTable* AssetManager::ZipSet::getZipResourceTable(const String8& path) 1959 { 1960 int idx = getIndex(path); 1961 sp<SharedZip> zip = mZipFile[idx]; 1962 if (zip == NULL) { 1963 zip = SharedZip::get(path); 1964 mZipFile.editItemAt(idx) = zip; 1965 } 1966 return zip->getResourceTable(); 1967 } 1968 1969 ResTable* AssetManager::ZipSet::setZipResourceTable(const String8& path, 1970 ResTable* res) 1971 { 1972 int idx = getIndex(path); 1973 sp<SharedZip> zip = mZipFile[idx]; 1974 // doesn't make sense to call before previously accessing. 1975 return zip->setResourceTable(res); 1976 } 1977 1978 /* 1979 * Generate the partial pathname for the specified archive. The caller 1980 * gets to prepend the asset root directory. 1981 * 1982 * Returns something like "common/en-US-noogle.jar". 1983 */ 1984 /*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath) 1985 { 1986 return String8(zipPath); 1987 } 1988 1989 bool AssetManager::ZipSet::isUpToDate() 1990 { 1991 const size_t N = mZipFile.size(); 1992 for (size_t i=0; i<N; i++) { 1993 if (mZipFile[i] != NULL && !mZipFile[i]->isUpToDate()) { 1994 return false; 1995 } 1996 } 1997 return true; 1998 } 1999 2000 /* 2001 * Compute the zip file's index. 2002 * 2003 * "appName", "locale", and "vendor" should be set to NULL to indicate the 2004 * default directory. 2005 */ 2006 int AssetManager::ZipSet::getIndex(const String8& zip) const 2007 { 2008 const size_t N = mZipPath.size(); 2009 for (size_t i=0; i<N; i++) { 2010 if (mZipPath[i] == zip) { 2011 return i; 2012 } 2013 } 2014 2015 mZipPath.add(zip); 2016 mZipFile.add(NULL); 2017 2018 return mZipPath.size()-1; 2019 } 2020