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