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