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