AssetManager.cpp revision bb9ea30ea9e390e69602935571795d2c80dc7b91
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
40using 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 */
46static const char* kDefaultLocale = "default";
47static const char* kDefaultVendor = "default";
48static const char* kAssetsRoot = "assets";
49static const char* kAppZipName = NULL; //"classes.jar";
50static const char* kSystemAssets = "framework/framework-res.apk";
51
52static const char* kExcludeExtension = ".EXCLUDE";
53
54static Asset* const kExcludedAsset = (Asset*) 0xd000000d;
55
56static volatile int32_t gCount = 0;
57
58
59/*
60 * ===========================================================================
61 *      AssetManager
62 * ===========================================================================
63 */
64
65int32_t AssetManager::getGlobalCount()
66{
67    return gCount;
68}
69
70AssetManager::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
80AssetManager::~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
93bool 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
139bool 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
150void* 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
157String8 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 */
173void AssetManager::setLocale(const char* locale)
174{
175    AutoMutex _l(mLock);
176    setLocaleLocked(locale);
177}
178
179void 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 */
198void 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
211void 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 */
259Asset* 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 */
296Asset* 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
324Asset* 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 */
355FileType 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
373const 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        bool shared = true;
399        const asset_path& ap = mAssetPaths.itemAt(i);
400        LOGV("Looking for resource asset in '%s'\n", ap.path.string());
401        if (ap.type != kFileTypeDirectory) {
402            ass = const_cast<AssetManager*>(this)->
403                mZipSet.getZipResourceTable(ap.path);
404            if (ass == NULL) {
405                LOGV("loading resource table %s\n", ap.path.string());
406                ass = const_cast<AssetManager*>(this)->
407                    openNonAssetInPathLocked("resources.arsc",
408                                             Asset::ACCESS_BUFFER,
409                                             ap);
410                if (ass != NULL && ass != kExcludedAsset) {
411                    ass = const_cast<AssetManager*>(this)->
412                        mZipSet.setZipResourceTable(ap.path, ass);
413                }
414            }
415        } else {
416            LOGV("loading resource table %s\n", ap.path.string());
417            Asset* ass = const_cast<AssetManager*>(this)->
418                openNonAssetInPathLocked("resources.arsc",
419                                         Asset::ACCESS_BUFFER,
420                                         ap);
421            shared = false;
422        }
423        if (ass != NULL && ass != kExcludedAsset) {
424            if (rt == NULL) {
425                mResources = rt = new ResTable();
426                updateResourceParamsLocked();
427            }
428            LOGV("Installing resource asset %p in to table %p\n", ass, mResources);
429            rt->add(ass, (void*)(i+1), !shared);
430
431            if (!shared) {
432                delete ass;
433            }
434        }
435    }
436
437    if (required && !rt) LOGW("Unable to find resources file resources.arsc");
438    if (!rt) {
439        mResources = rt = new ResTable();
440    }
441    return rt;
442}
443
444void AssetManager::updateResourceParamsLocked() const
445{
446    ResTable* res = mResources;
447    if (!res) {
448        return;
449    }
450
451    size_t llen = mLocale ? strlen(mLocale) : 0;
452    mConfig->language[0] = 0;
453    mConfig->language[1] = 0;
454    mConfig->country[0] = 0;
455    mConfig->country[1] = 0;
456    if (llen >= 2) {
457        mConfig->language[0] = mLocale[0];
458        mConfig->language[1] = mLocale[1];
459    }
460    if (llen >= 5) {
461        mConfig->country[0] = mLocale[3];
462        mConfig->country[1] = mLocale[4];
463    }
464    mConfig->size = sizeof(*mConfig);
465
466    res->setParameters(mConfig);
467}
468
469const ResTable& AssetManager::getResources(bool required) const
470{
471    const ResTable* rt = getResTable(required);
472    return *rt;
473}
474
475bool AssetManager::isUpToDate()
476{
477    AutoMutex _l(mLock);
478    return mZipSet.isUpToDate();
479}
480
481void AssetManager::getLocales(Vector<String8>* locales) const
482{
483    ResTable* res = mResources;
484    if (res != NULL) {
485        res->getLocales(locales);
486    }
487}
488
489/*
490 * Open a non-asset file as if it were an asset, searching for it in the
491 * specified app.
492 *
493 * Pass in a NULL values for "appName" if the common app directory should
494 * be used.
495 */
496Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
497    const asset_path& ap)
498{
499    Asset* pAsset = NULL;
500
501    /* look at the filesystem on disk */
502    if (ap.type == kFileTypeDirectory) {
503        String8 path(ap.path);
504        path.appendPath(fileName);
505
506        pAsset = openAssetFromFileLocked(path, mode);
507
508        if (pAsset == NULL) {
509            /* try again, this time with ".gz" */
510            path.append(".gz");
511            pAsset = openAssetFromFileLocked(path, mode);
512        }
513
514        if (pAsset != NULL) {
515            //printf("FOUND NA '%s' on disk\n", fileName);
516            pAsset->setAssetSource(path);
517        }
518
519    /* look inside the zip file */
520    } else {
521        String8 path(fileName);
522
523        /* check the appropriate Zip file */
524        ZipFileRO* pZip;
525        ZipEntryRO entry;
526
527        pZip = getZipFileLocked(ap);
528        if (pZip != NULL) {
529            //printf("GOT zip, checking NA '%s'\n", (const char*) path);
530            entry = pZip->findEntryByName(path.string());
531            if (entry != NULL) {
532                //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
533                pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
534            }
535        }
536
537        if (pAsset != NULL) {
538            /* create a "source" name, for debug/display */
539            pAsset->setAssetSource(
540                    createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
541                                                String8(fileName)));
542        }
543    }
544
545    return pAsset;
546}
547
548/*
549 * Open an asset, searching for it in the directory hierarchy for the
550 * specified app.
551 *
552 * Pass in a NULL values for "appName" if the common app directory should
553 * be used.
554 */
555Asset* AssetManager::openInPathLocked(const char* fileName, AccessMode mode,
556    const asset_path& ap)
557{
558    Asset* pAsset = NULL;
559
560    /*
561     * Try various combinations of locale and vendor.
562     */
563    if (mLocale != NULL && mVendor != NULL)
564        pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, mVendor);
565    if (pAsset == NULL && mVendor != NULL)
566        pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, mVendor);
567    if (pAsset == NULL && mLocale != NULL)
568        pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, NULL);
569    if (pAsset == NULL)
570        pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, NULL);
571
572    return pAsset;
573}
574
575/*
576 * Open an asset, searching for it in the directory hierarchy for the
577 * specified locale and vendor.
578 *
579 * We also search in "app.jar".
580 *
581 * Pass in NULL values for "appName", "locale", and "vendor" if the
582 * defaults should be used.
583 */
584Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode mode,
585    const asset_path& ap, const char* locale, const char* vendor)
586{
587    Asset* pAsset = NULL;
588
589    if (ap.type == kFileTypeDirectory) {
590        if (mCacheMode == CACHE_OFF) {
591            /* look at the filesystem on disk */
592            String8 path(createPathNameLocked(ap, locale, vendor));
593            path.appendPath(fileName);
594
595            String8 excludeName(path);
596            excludeName.append(kExcludeExtension);
597            if (::getFileType(excludeName.string()) != kFileTypeNonexistent) {
598                /* say no more */
599                //printf("+++ excluding '%s'\n", (const char*) excludeName);
600                return kExcludedAsset;
601            }
602
603            pAsset = openAssetFromFileLocked(path, mode);
604
605            if (pAsset == NULL) {
606                /* try again, this time with ".gz" */
607                path.append(".gz");
608                pAsset = openAssetFromFileLocked(path, mode);
609            }
610
611            if (pAsset != NULL)
612                pAsset->setAssetSource(path);
613        } else {
614            /* find in cache */
615            String8 path(createPathNameLocked(ap, locale, vendor));
616            path.appendPath(fileName);
617
618            AssetDir::FileInfo tmpInfo;
619            bool found = false;
620
621            String8 excludeName(path);
622            excludeName.append(kExcludeExtension);
623
624            if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) {
625                /* go no farther */
626                //printf("+++ Excluding '%s'\n", (const char*) excludeName);
627                return kExcludedAsset;
628            }
629
630            /*
631             * File compression extensions (".gz") don't get stored in the
632             * name cache, so we have to try both here.
633             */
634            if (mCache.indexOf(path) != NAME_NOT_FOUND) {
635                found = true;
636                pAsset = openAssetFromFileLocked(path, mode);
637                if (pAsset == NULL) {
638                    /* try again, this time with ".gz" */
639                    path.append(".gz");
640                    pAsset = openAssetFromFileLocked(path, mode);
641                }
642            }
643
644            if (pAsset != NULL)
645                pAsset->setAssetSource(path);
646
647            /*
648             * Don't continue the search into the Zip files.  Our cached info
649             * said it was a file on disk; to be consistent with openDir()
650             * we want to return the loose asset.  If the cached file gets
651             * removed, we fail.
652             *
653             * The alternative is to update our cache when files get deleted,
654             * or make some sort of "best effort" promise, but for now I'm
655             * taking the hard line.
656             */
657            if (found) {
658                if (pAsset == NULL)
659                    LOGD("Expected file not found: '%s'\n", path.string());
660                return pAsset;
661            }
662        }
663    }
664
665    /*
666     * Either it wasn't found on disk or on the cached view of the disk.
667     * Dig through the currently-opened set of Zip files.  If caching
668     * is disabled, the Zip file may get reopened.
669     */
670    if (pAsset == NULL && ap.type == kFileTypeRegular) {
671        String8 path;
672
673        path.appendPath((locale != NULL) ? locale : kDefaultLocale);
674        path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
675        path.appendPath(fileName);
676
677        /* check the appropriate Zip file */
678        ZipFileRO* pZip;
679        ZipEntryRO entry;
680
681        pZip = getZipFileLocked(ap);
682        if (pZip != NULL) {
683            //printf("GOT zip, checking '%s'\n", (const char*) path);
684            entry = pZip->findEntryByName(path.string());
685            if (entry != NULL) {
686                //printf("FOUND in Zip file for %s/%s-%s\n",
687                //    appName, locale, vendor);
688                pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
689            }
690        }
691
692        if (pAsset != NULL) {
693            /* create a "source" name, for debug/display */
694            pAsset->setAssetSource(createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()),
695                                                             String8(""), String8(fileName)));
696        }
697    }
698
699    return pAsset;
700}
701
702/*
703 * Create a "source name" for a file from a Zip archive.
704 */
705String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName,
706    const String8& dirName, const String8& fileName)
707{
708    String8 sourceName("zip:");
709    sourceName.append(zipFileName);
710    sourceName.append(":");
711    if (dirName.length() > 0) {
712        sourceName.appendPath(dirName);
713    }
714    sourceName.appendPath(fileName);
715    return sourceName;
716}
717
718/*
719 * Create a path to a loose asset (asset-base/app/locale/vendor).
720 */
721String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* locale,
722    const char* vendor)
723{
724    String8 path(ap.path);
725    path.appendPath((locale != NULL) ? locale : kDefaultLocale);
726    path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
727    return path;
728}
729
730/*
731 * Create a path to a loose asset (asset-base/app/rootDir).
732 */
733String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir)
734{
735    String8 path(ap.path);
736    if (rootDir != NULL) path.appendPath(rootDir);
737    return path;
738}
739
740/*
741 * Return a pointer to one of our open Zip archives.  Returns NULL if no
742 * matching Zip file exists.
743 *
744 * Right now we have 2 possible Zip files (1 each in app/"common").
745 *
746 * If caching is set to CACHE_OFF, to get the expected behavior we
747 * need to reopen the Zip file on every request.  That would be silly
748 * and expensive, so instead we just check the file modification date.
749 *
750 * Pass in NULL values for "appName", "locale", and "vendor" if the
751 * generics should be used.
752 */
753ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
754{
755    LOGV("getZipFileLocked() in %p\n", this);
756
757    return mZipSet.getZip(ap.path);
758}
759
760/*
761 * Try to open an asset from a file on disk.
762 *
763 * If the file is compressed with gzip, we seek to the start of the
764 * deflated data and pass that in (just like we would for a Zip archive).
765 *
766 * For uncompressed data, we may already have an mmap()ed version sitting
767 * around.  If so, we want to hand that to the Asset instead.
768 *
769 * This returns NULL if the file doesn't exist, couldn't be opened, or
770 * claims to be a ".gz" but isn't.
771 */
772Asset* AssetManager::openAssetFromFileLocked(const String8& pathName,
773    AccessMode mode)
774{
775    Asset* pAsset = NULL;
776
777    if (strcasecmp(pathName.getPathExtension().string(), ".gz") == 0) {
778        //printf("TRYING '%s'\n", (const char*) pathName);
779        pAsset = Asset::createFromCompressedFile(pathName.string(), mode);
780    } else {
781        //printf("TRYING '%s'\n", (const char*) pathName);
782        pAsset = Asset::createFromFile(pathName.string(), mode);
783    }
784
785    return pAsset;
786}
787
788/*
789 * Given an entry in a Zip archive, create a new Asset object.
790 *
791 * If the entry is uncompressed, we may want to create or share a
792 * slice of shared memory.
793 */
794Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile,
795    const ZipEntryRO entry, AccessMode mode, const String8& entryName)
796{
797    Asset* pAsset = NULL;
798
799    // TODO: look for previously-created shared memory slice?
800    int method;
801    long uncompressedLen;
802
803    //printf("USING Zip '%s'\n", pEntry->getFileName());
804
805    //pZipFile->getEntryInfo(entry, &method, &uncompressedLen, &compressedLen,
806    //    &offset);
807    if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL,
808            NULL, NULL))
809    {
810        LOGW("getEntryInfo failed\n");
811        return NULL;
812    }
813
814    FileMap* dataMap = pZipFile->createEntryFileMap(entry);
815    if (dataMap == NULL) {
816        LOGW("create map from entry failed\n");
817        return NULL;
818    }
819
820    if (method == ZipFileRO::kCompressStored) {
821        pAsset = Asset::createFromUncompressedMap(dataMap, mode);
822        LOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(),
823                dataMap->getFileName(), mode, pAsset);
824    } else {
825        pAsset = Asset::createFromCompressedMap(dataMap, method,
826            uncompressedLen, mode);
827        LOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(),
828                dataMap->getFileName(), mode, pAsset);
829    }
830    if (pAsset == NULL) {
831        /* unexpected */
832        LOGW("create from segment failed\n");
833    }
834
835    return pAsset;
836}
837
838
839
840/*
841 * Open a directory in the asset namespace.
842 *
843 * An "asset directory" is simply the combination of all files in all
844 * locations, with ".gz" stripped for loose files.  With app, locale, and
845 * vendor defined, we have 8 directories and 2 Zip archives to scan.
846 *
847 * Pass in "" for the root dir.
848 */
849AssetDir* AssetManager::openDir(const char* dirName)
850{
851    AutoMutex _l(mLock);
852
853    AssetDir* pDir = NULL;
854    SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL;
855
856    LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
857    assert(dirName != NULL);
858
859    //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
860
861    if (mCacheMode != CACHE_OFF && !mCacheValid)
862        loadFileNameCacheLocked();
863
864    pDir = new AssetDir;
865
866    /*
867     * Scan the various directories, merging what we find into a single
868     * vector.  We want to scan them in reverse priority order so that
869     * the ".EXCLUDE" processing works correctly.  Also, if we decide we
870     * want to remember where the file is coming from, we'll get the right
871     * version.
872     *
873     * We start with Zip archives, then do loose files.
874     */
875    pMergedInfo = new SortedVector<AssetDir::FileInfo>;
876
877    size_t i = mAssetPaths.size();
878    while (i > 0) {
879        i--;
880        const asset_path& ap = mAssetPaths.itemAt(i);
881        if (ap.type == kFileTypeRegular) {
882            LOGV("Adding directory %s from zip %s", dirName, ap.path.string());
883            scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName);
884        } else {
885            LOGV("Adding directory %s from dir %s", dirName, ap.path.string());
886            scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName);
887        }
888    }
889
890#if 0
891    printf("FILE LIST:\n");
892    for (i = 0; i < (size_t) pMergedInfo->size(); i++) {
893        printf(" %d: (%d) '%s'\n", i,
894            pMergedInfo->itemAt(i).getFileType(),
895            (const char*) pMergedInfo->itemAt(i).getFileName());
896    }
897#endif
898
899    pDir->setFileList(pMergedInfo);
900    return pDir;
901}
902
903/*
904 * Open a directory in the non-asset namespace.
905 *
906 * An "asset directory" is simply the combination of all files in all
907 * locations, with ".gz" stripped for loose files.  With app, locale, and
908 * vendor defined, we have 8 directories and 2 Zip archives to scan.
909 *
910 * Pass in "" for the root dir.
911 */
912AssetDir* AssetManager::openNonAssetDir(void* cookie, const char* dirName)
913{
914    AutoMutex _l(mLock);
915
916    AssetDir* pDir = NULL;
917    SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL;
918
919    LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
920    assert(dirName != NULL);
921
922    //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
923
924    if (mCacheMode != CACHE_OFF && !mCacheValid)
925        loadFileNameCacheLocked();
926
927    pDir = new AssetDir;
928
929    pMergedInfo = new SortedVector<AssetDir::FileInfo>;
930
931    const size_t which = ((size_t)cookie)-1;
932
933    if (which < mAssetPaths.size()) {
934        const asset_path& ap = mAssetPaths.itemAt(which);
935        if (ap.type == kFileTypeRegular) {
936            LOGV("Adding directory %s from zip %s", dirName, ap.path.string());
937            scanAndMergeZipLocked(pMergedInfo, ap, NULL, dirName);
938        } else {
939            LOGV("Adding directory %s from dir %s", dirName, ap.path.string());
940            scanAndMergeDirLocked(pMergedInfo, ap, NULL, dirName);
941        }
942    }
943
944#if 0
945    printf("FILE LIST:\n");
946    for (i = 0; i < (size_t) pMergedInfo->size(); i++) {
947        printf(" %d: (%d) '%s'\n", i,
948            pMergedInfo->itemAt(i).getFileType(),
949            (const char*) pMergedInfo->itemAt(i).getFileName());
950    }
951#endif
952
953    pDir->setFileList(pMergedInfo);
954    return pDir;
955}
956
957/*
958 * Scan the contents of the specified directory and merge them into the
959 * "pMergedInfo" vector, removing previous entries if we find "exclude"
960 * directives.
961 *
962 * Returns "false" if we found nothing to contribute.
963 */
964bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
965    const asset_path& ap, const char* rootDir, const char* dirName)
966{
967    SortedVector<AssetDir::FileInfo>* pContents;
968    String8 path;
969
970    assert(pMergedInfo != NULL);
971
972    //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName);
973
974    if (mCacheValid) {
975        int i, start, count;
976
977        pContents = new SortedVector<AssetDir::FileInfo>;
978
979        /*
980         * Get the basic partial path and find it in the cache.  That's
981         * the start point for the search.
982         */
983        path = createPathNameLocked(ap, rootDir);
984        if (dirName[0] != '\0')
985            path.appendPath(dirName);
986
987        start = mCache.indexOf(path);
988        if (start == NAME_NOT_FOUND) {
989            //printf("+++ not found in cache: dir '%s'\n", (const char*) path);
990            delete pContents;
991            return false;
992        }
993
994        /*
995         * The match string looks like "common/default/default/foo/bar/".
996         * The '/' on the end ensures that we don't match on the directory
997         * itself or on ".../foo/barfy/".
998         */
999        path.append("/");
1000
1001        count = mCache.size();
1002
1003        /*
1004         * Pick out the stuff in the current dir by examining the pathname.
1005         * It needs to match the partial pathname prefix, and not have a '/'
1006         * (fssep) anywhere after the prefix.
1007         */
1008        for (i = start+1; i < count; i++) {
1009            if (mCache[i].getFileName().length() > path.length() &&
1010                strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0)
1011            {
1012                const char* name = mCache[i].getFileName().string();
1013                // XXX THIS IS BROKEN!  Looks like we need to store the full
1014                // path prefix separately from the file path.
1015                if (strchr(name + path.length(), '/') == NULL) {
1016                    /* grab it, reducing path to just the filename component */
1017                    AssetDir::FileInfo tmp = mCache[i];
1018                    tmp.setFileName(tmp.getFileName().getPathLeaf());
1019                    pContents->add(tmp);
1020                }
1021            } else {
1022                /* no longer in the dir or its subdirs */
1023                break;
1024            }
1025
1026        }
1027    } else {
1028        path = createPathNameLocked(ap, rootDir);
1029        if (dirName[0] != '\0')
1030            path.appendPath(dirName);
1031        pContents = scanDirLocked(path);
1032        if (pContents == NULL)
1033            return false;
1034    }
1035
1036    // if we wanted to do an incremental cache fill, we would do it here
1037
1038    /*
1039     * Process "exclude" directives.  If we find a filename that ends with
1040     * ".EXCLUDE", we look for a matching entry in the "merged" set, and
1041     * remove it if we find it.  We also delete the "exclude" entry.
1042     */
1043    int i, count, exclExtLen;
1044
1045    count = pContents->size();
1046    exclExtLen = strlen(kExcludeExtension);
1047    for (i = 0; i < count; i++) {
1048        const char* name;
1049        int nameLen;
1050
1051        name = pContents->itemAt(i).getFileName().string();
1052        nameLen = strlen(name);
1053        if (nameLen > exclExtLen &&
1054            strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0)
1055        {
1056            String8 match(name, nameLen - exclExtLen);
1057            int matchIdx;
1058
1059            matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match);
1060            if (matchIdx > 0) {
1061                LOGV("Excluding '%s' [%s]\n",
1062                    pMergedInfo->itemAt(matchIdx).getFileName().string(),
1063                    pMergedInfo->itemAt(matchIdx).getSourceName().string());
1064                pMergedInfo->removeAt(matchIdx);
1065            } else {
1066                //printf("+++ no match on '%s'\n", (const char*) match);
1067            }
1068
1069            LOGD("HEY: size=%d removing %d\n", (int)pContents->size(), i);
1070            pContents->removeAt(i);
1071            i--;        // adjust "for" loop
1072            count--;    //  and loop limit
1073        }
1074    }
1075
1076    mergeInfoLocked(pMergedInfo, pContents);
1077
1078    delete pContents;
1079
1080    return true;
1081}
1082
1083/*
1084 * Scan the contents of the specified directory, and stuff what we find
1085 * into a newly-allocated vector.
1086 *
1087 * Files ending in ".gz" will have their extensions removed.
1088 *
1089 * We should probably think about skipping files with "illegal" names,
1090 * e.g. illegal characters (/\:) or excessive length.
1091 *
1092 * Returns NULL if the specified directory doesn't exist.
1093 */
1094SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& path)
1095{
1096    SortedVector<AssetDir::FileInfo>* pContents = NULL;
1097    DIR* dir;
1098    struct dirent* entry;
1099    FileType fileType;
1100
1101    LOGV("Scanning dir '%s'\n", path.string());
1102
1103    dir = opendir(path.string());
1104    if (dir == NULL)
1105        return NULL;
1106
1107    pContents = new SortedVector<AssetDir::FileInfo>;
1108
1109    while (1) {
1110        entry = readdir(dir);
1111        if (entry == NULL)
1112            break;
1113
1114        if (strcmp(entry->d_name, ".") == 0 ||
1115            strcmp(entry->d_name, "..") == 0)
1116            continue;
1117
1118#ifdef _DIRENT_HAVE_D_TYPE
1119        if (entry->d_type == DT_REG)
1120            fileType = kFileTypeRegular;
1121        else if (entry->d_type == DT_DIR)
1122            fileType = kFileTypeDirectory;
1123        else
1124            fileType = kFileTypeUnknown;
1125#else
1126        // stat the file
1127        fileType = ::getFileType(path.appendPathCopy(entry->d_name).string());
1128#endif
1129
1130        if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory)
1131            continue;
1132
1133        AssetDir::FileInfo info;
1134        info.set(String8(entry->d_name), fileType);
1135        if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0)
1136            info.setFileName(info.getFileName().getBasePath());
1137        info.setSourceName(path.appendPathCopy(info.getFileName()));
1138        pContents->add(info);
1139    }
1140
1141    closedir(dir);
1142    return pContents;
1143}
1144
1145/*
1146 * Scan the contents out of the specified Zip archive, and merge what we
1147 * find into "pMergedInfo".  If the Zip archive in question doesn't exist,
1148 * we return immediately.
1149 *
1150 * Returns "false" if we found nothing to contribute.
1151 */
1152bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1153    const asset_path& ap, const char* rootDir, const char* baseDirName)
1154{
1155    ZipFileRO* pZip;
1156    Vector<String8> dirs;
1157    AssetDir::FileInfo info;
1158    SortedVector<AssetDir::FileInfo> contents;
1159    String8 sourceName, zipName, dirName;
1160
1161    pZip = mZipSet.getZip(ap.path);
1162    if (pZip == NULL) {
1163        LOGW("Failure opening zip %s\n", ap.path.string());
1164        return false;
1165    }
1166
1167    zipName = ZipSet::getPathName(ap.path.string());
1168
1169    /* convert "sounds" to "rootDir/sounds" */
1170    if (rootDir != NULL) dirName = rootDir;
1171    dirName.appendPath(baseDirName);
1172
1173    /*
1174     * Scan through the list of files, looking for a match.  The files in
1175     * the Zip table of contents are not in sorted order, so we have to
1176     * process the entire list.  We're looking for a string that begins
1177     * with the characters in "dirName", is followed by a '/', and has no
1178     * subsequent '/' in the stuff that follows.
1179     *
1180     * What makes this especially fun is that directories are not stored
1181     * explicitly in Zip archives, so we have to infer them from context.
1182     * When we see "sounds/foo.wav" we have to leave a note to ourselves
1183     * to insert a directory called "sounds" into the list.  We store
1184     * these in temporary vector so that we only return each one once.
1185     *
1186     * Name comparisons are case-sensitive to match UNIX filesystem
1187     * semantics.
1188     */
1189    int dirNameLen = dirName.length();
1190    for (int i = 0; i < pZip->getNumEntries(); i++) {
1191        ZipEntryRO entry;
1192        char nameBuf[256];
1193
1194        entry = pZip->findEntryByIndex(i);
1195        if (pZip->getEntryFileName(entry, nameBuf, sizeof(nameBuf)) != 0) {
1196            // TODO: fix this if we expect to have long names
1197            LOGE("ARGH: name too long?\n");
1198            continue;
1199        }
1200        //printf("Comparing %s in %s?\n", nameBuf, dirName.string());
1201        if (dirNameLen == 0 ||
1202            (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 &&
1203             nameBuf[dirNameLen] == '/'))
1204        {
1205            const char* cp;
1206            const char* nextSlash;
1207
1208            cp = nameBuf + dirNameLen;
1209            if (dirNameLen != 0)
1210                cp++;       // advance past the '/'
1211
1212            nextSlash = strchr(cp, '/');
1213//xxx this may break if there are bare directory entries
1214            if (nextSlash == NULL) {
1215                /* this is a file in the requested directory */
1216
1217                info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular);
1218
1219                info.setSourceName(
1220                    createZipSourceNameLocked(zipName, dirName, info.getFileName()));
1221
1222                contents.add(info);
1223                //printf("FOUND: file '%s'\n", info.getFileName().string());
1224            } else {
1225                /* this is a subdir; add it if we don't already have it*/
1226                String8 subdirName(cp, nextSlash - cp);
1227                size_t j;
1228                size_t N = dirs.size();
1229
1230                for (j = 0; j < N; j++) {
1231                    if (subdirName == dirs[j]) {
1232                        break;
1233                    }
1234                }
1235                if (j == N) {
1236                    dirs.add(subdirName);
1237                }
1238
1239                //printf("FOUND: dir '%s'\n", subdirName.string());
1240            }
1241        }
1242    }
1243
1244    /*
1245     * Add the set of unique directories.
1246     */
1247    for (int i = 0; i < (int) dirs.size(); i++) {
1248        info.set(dirs[i], kFileTypeDirectory);
1249        info.setSourceName(
1250            createZipSourceNameLocked(zipName, dirName, info.getFileName()));
1251        contents.add(info);
1252    }
1253
1254    mergeInfoLocked(pMergedInfo, &contents);
1255
1256    return true;
1257}
1258
1259
1260/*
1261 * Merge two vectors of FileInfo.
1262 *
1263 * The merged contents will be stuffed into *pMergedInfo.
1264 *
1265 * If an entry for a file exists in both "pMergedInfo" and "pContents",
1266 * we use the newer "pContents" entry.
1267 */
1268void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1269    const SortedVector<AssetDir::FileInfo>* pContents)
1270{
1271    /*
1272     * Merge what we found in this directory with what we found in
1273     * other places.
1274     *
1275     * Two basic approaches:
1276     * (1) Create a new array that holds the unique values of the two
1277     *     arrays.
1278     * (2) Take the elements from pContents and shove them into pMergedInfo.
1279     *
1280     * Because these are vectors of complex objects, moving elements around
1281     * inside the vector requires constructing new objects and allocating
1282     * storage for members.  With approach #1, we're always adding to the
1283     * end, whereas with #2 we could be inserting multiple elements at the
1284     * front of the vector.  Approach #1 requires a full copy of the
1285     * contents of pMergedInfo, but approach #2 requires the same copy for
1286     * every insertion at the front of pMergedInfo.
1287     *
1288     * (We should probably use a SortedVector interface that allows us to
1289     * just stuff items in, trusting us to maintain the sort order.)
1290     */
1291    SortedVector<AssetDir::FileInfo>* pNewSorted;
1292    int mergeMax, contMax;
1293    int mergeIdx, contIdx;
1294
1295    pNewSorted = new SortedVector<AssetDir::FileInfo>;
1296    mergeMax = pMergedInfo->size();
1297    contMax = pContents->size();
1298    mergeIdx = contIdx = 0;
1299
1300    while (mergeIdx < mergeMax || contIdx < contMax) {
1301        if (mergeIdx == mergeMax) {
1302            /* hit end of "merge" list, copy rest of "contents" */
1303            pNewSorted->add(pContents->itemAt(contIdx));
1304            contIdx++;
1305        } else if (contIdx == contMax) {
1306            /* hit end of "cont" list, copy rest of "merge" */
1307            pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
1308            mergeIdx++;
1309        } else if (pMergedInfo->itemAt(mergeIdx) == pContents->itemAt(contIdx))
1310        {
1311            /* items are identical, add newer and advance both indices */
1312            pNewSorted->add(pContents->itemAt(contIdx));
1313            mergeIdx++;
1314            contIdx++;
1315        } else if (pMergedInfo->itemAt(mergeIdx) < pContents->itemAt(contIdx))
1316        {
1317            /* "merge" is lower, add that one */
1318            pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
1319            mergeIdx++;
1320        } else {
1321            /* "cont" is lower, add that one */
1322            assert(pContents->itemAt(contIdx) < pMergedInfo->itemAt(mergeIdx));
1323            pNewSorted->add(pContents->itemAt(contIdx));
1324            contIdx++;
1325        }
1326    }
1327
1328    /*
1329     * Overwrite the "merged" list with the new stuff.
1330     */
1331    *pMergedInfo = *pNewSorted;
1332    delete pNewSorted;
1333
1334#if 0       // for Vector, rather than SortedVector
1335    int i, j;
1336    for (i = pContents->size() -1; i >= 0; i--) {
1337        bool add = true;
1338
1339        for (j = pMergedInfo->size() -1; j >= 0; j--) {
1340            /* case-sensitive comparisons, to behave like UNIX fs */
1341            if (strcmp(pContents->itemAt(i).mFileName,
1342                       pMergedInfo->itemAt(j).mFileName) == 0)
1343            {
1344                /* match, don't add this entry */
1345                add = false;
1346                break;
1347            }
1348        }
1349
1350        if (add)
1351            pMergedInfo->add(pContents->itemAt(i));
1352    }
1353#endif
1354}
1355
1356
1357/*
1358 * Load all files into the file name cache.  We want to do this across
1359 * all combinations of { appname, locale, vendor }, performing a recursive
1360 * directory traversal.
1361 *
1362 * This is not the most efficient data structure.  Also, gathering the
1363 * information as we needed it (file-by-file or directory-by-directory)
1364 * would be faster.  However, on the actual device, 99% of the files will
1365 * live in Zip archives, so this list will be very small.  The trouble
1366 * is that we have to check the "loose" files first, so it's important
1367 * that we don't beat the filesystem silly looking for files that aren't
1368 * there.
1369 *
1370 * Note on thread safety: this is the only function that causes updates
1371 * to mCache, and anybody who tries to use it will call here if !mCacheValid,
1372 * so we need to employ a mutex here.
1373 */
1374void AssetManager::loadFileNameCacheLocked(void)
1375{
1376    assert(!mCacheValid);
1377    assert(mCache.size() == 0);
1378
1379#ifdef DO_TIMINGS   // need to link against -lrt for this now
1380    DurationTimer timer;
1381    timer.start();
1382#endif
1383
1384    fncScanLocked(&mCache, "");
1385
1386#ifdef DO_TIMINGS
1387    timer.stop();
1388    LOGD("Cache scan took %.3fms\n",
1389        timer.durationUsecs() / 1000.0);
1390#endif
1391
1392#if 0
1393    int i;
1394    printf("CACHED FILE LIST (%d entries):\n", mCache.size());
1395    for (i = 0; i < (int) mCache.size(); i++) {
1396        printf(" %d: (%d) '%s'\n", i,
1397            mCache.itemAt(i).getFileType(),
1398            (const char*) mCache.itemAt(i).getFileName());
1399    }
1400#endif
1401
1402    mCacheValid = true;
1403}
1404
1405/*
1406 * Scan up to 8 versions of the specified directory.
1407 */
1408void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1409    const char* dirName)
1410{
1411    size_t i = mAssetPaths.size();
1412    while (i > 0) {
1413        i--;
1414        const asset_path& ap = mAssetPaths.itemAt(i);
1415        fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName);
1416        if (mLocale != NULL)
1417            fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName);
1418        if (mVendor != NULL)
1419            fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName);
1420        if (mLocale != NULL && mVendor != NULL)
1421            fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName);
1422    }
1423}
1424
1425/*
1426 * Recursively scan this directory and all subdirs.
1427 *
1428 * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE
1429 * files, and we prepend the extended partial path to the filenames.
1430 */
1431bool AssetManager::fncScanAndMergeDirLocked(
1432    SortedVector<AssetDir::FileInfo>* pMergedInfo,
1433    const asset_path& ap, const char* locale, const char* vendor,
1434    const char* dirName)
1435{
1436    SortedVector<AssetDir::FileInfo>* pContents;
1437    String8 partialPath;
1438    String8 fullPath;
1439
1440    // XXX This is broken -- the filename cache needs to hold the base
1441    // asset path separately from its filename.
1442
1443    partialPath = createPathNameLocked(ap, locale, vendor);
1444    if (dirName[0] != '\0') {
1445        partialPath.appendPath(dirName);
1446    }
1447
1448    fullPath = partialPath;
1449    pContents = scanDirLocked(fullPath);
1450    if (pContents == NULL) {
1451        return false;       // directory did not exist
1452    }
1453
1454    /*
1455     * Scan all subdirectories of the current dir, merging what we find
1456     * into "pMergedInfo".
1457     */
1458    for (int i = 0; i < (int) pContents->size(); i++) {
1459        if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) {
1460            String8 subdir(dirName);
1461            subdir.appendPath(pContents->itemAt(i).getFileName());
1462
1463            fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string());
1464        }
1465    }
1466
1467    /*
1468     * To be consistent, we want entries for the root directory.  If
1469     * we're the root, add one now.
1470     */
1471    if (dirName[0] == '\0') {
1472        AssetDir::FileInfo tmpInfo;
1473
1474        tmpInfo.set(String8(""), kFileTypeDirectory);
1475        tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor));
1476        pContents->add(tmpInfo);
1477    }
1478
1479    /*
1480     * We want to prepend the extended partial path to every entry in
1481     * "pContents".  It's the same value for each entry, so this will
1482     * not change the sorting order of the vector contents.
1483     */
1484    for (int i = 0; i < (int) pContents->size(); i++) {
1485        const AssetDir::FileInfo& info = pContents->itemAt(i);
1486        pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName()));
1487    }
1488
1489    mergeInfoLocked(pMergedInfo, pContents);
1490    return true;
1491}
1492
1493/*
1494 * Trash the cache.
1495 */
1496void AssetManager::purgeFileNameCacheLocked(void)
1497{
1498    mCacheValid = false;
1499    mCache.clear();
1500}
1501
1502/*
1503 * ===========================================================================
1504 *      AssetManager::SharedZip
1505 * ===========================================================================
1506 */
1507
1508
1509Mutex AssetManager::SharedZip::gLock;
1510DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen;
1511
1512AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
1513    : mPath(path), mZipFile(NULL), mModWhen(modWhen), mResourceTableAsset(NULL)
1514{
1515    //LOGI("Creating SharedZip %p %s\n", this, (const char*)mPath);
1516    mZipFile = new ZipFileRO;
1517    LOGV("+++ opening zip '%s'\n", mPath.string());
1518    if (mZipFile->open(mPath.string()) != NO_ERROR) {
1519        LOGD("failed to open Zip archive '%s'\n", mPath.string());
1520        delete mZipFile;
1521        mZipFile = NULL;
1522    }
1523}
1524
1525sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path)
1526{
1527    AutoMutex _l(gLock);
1528    time_t modWhen = getFileModDate(path);
1529    sp<SharedZip> zip = gOpen.valueFor(path).promote();
1530    if (zip != NULL && zip->mModWhen == modWhen) {
1531        return zip;
1532    }
1533    zip = new SharedZip(path, modWhen);
1534    gOpen.add(path, zip);
1535    return zip;
1536
1537}
1538
1539ZipFileRO* AssetManager::SharedZip::getZip()
1540{
1541    return mZipFile;
1542}
1543
1544Asset* AssetManager::SharedZip::getResourceTableAsset()
1545{
1546    LOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
1547    return mResourceTableAsset;
1548}
1549
1550Asset* AssetManager::SharedZip::setResourceTableAsset(Asset* asset)
1551{
1552    {
1553        AutoMutex _l(gLock);
1554        if (mResourceTableAsset == NULL) {
1555            mResourceTableAsset = asset;
1556            // This is not thread safe the first time it is called, so
1557            // do it here with the global lock held.
1558            asset->getBuffer(true);
1559            return asset;
1560        }
1561    }
1562    delete asset;
1563    return mResourceTableAsset;
1564}
1565
1566bool AssetManager::SharedZip::isUpToDate()
1567{
1568    time_t modWhen = getFileModDate(mPath.string());
1569    return mModWhen == modWhen;
1570}
1571
1572AssetManager::SharedZip::~SharedZip()
1573{
1574    //LOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath);
1575    if (mResourceTableAsset != NULL) {
1576        delete mResourceTableAsset;
1577    }
1578    if (mZipFile != NULL) {
1579        delete mZipFile;
1580        LOGV("Closed '%s'\n", mPath.string());
1581    }
1582}
1583
1584/*
1585 * ===========================================================================
1586 *      AssetManager::ZipSet
1587 * ===========================================================================
1588 */
1589
1590/*
1591 * Constructor.
1592 */
1593AssetManager::ZipSet::ZipSet(void)
1594{
1595}
1596
1597/*
1598 * Destructor.  Close any open archives.
1599 */
1600AssetManager::ZipSet::~ZipSet(void)
1601{
1602    size_t N = mZipFile.size();
1603    for (size_t i = 0; i < N; i++)
1604        closeZip(i);
1605}
1606
1607/*
1608 * Close a Zip file and reset the entry.
1609 */
1610void AssetManager::ZipSet::closeZip(int idx)
1611{
1612    mZipFile.editItemAt(idx) = NULL;
1613}
1614
1615
1616/*
1617 * Retrieve the appropriate Zip file from the set.
1618 */
1619ZipFileRO* AssetManager::ZipSet::getZip(const String8& path)
1620{
1621    int idx = getIndex(path);
1622    sp<SharedZip> zip = mZipFile[idx];
1623    if (zip == NULL) {
1624        zip = SharedZip::get(path);
1625        mZipFile.editItemAt(idx) = zip;
1626    }
1627    return zip->getZip();
1628}
1629
1630Asset* AssetManager::ZipSet::getZipResourceTable(const String8& path)
1631{
1632    int idx = getIndex(path);
1633    sp<SharedZip> zip = mZipFile[idx];
1634    if (zip == NULL) {
1635        zip = SharedZip::get(path);
1636        mZipFile.editItemAt(idx) = zip;
1637    }
1638    return zip->getResourceTableAsset();
1639}
1640
1641Asset* AssetManager::ZipSet::setZipResourceTable(const String8& path,
1642                                                 Asset* asset)
1643{
1644    int idx = getIndex(path);
1645    sp<SharedZip> zip = mZipFile[idx];
1646    // doesn't make sense to call before previously accessing.
1647    return zip->setResourceTableAsset(asset);
1648}
1649
1650/*
1651 * Generate the partial pathname for the specified archive.  The caller
1652 * gets to prepend the asset root directory.
1653 *
1654 * Returns something like "common/en-US-noogle.jar".
1655 */
1656/*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath)
1657{
1658    return String8(zipPath);
1659}
1660
1661bool AssetManager::ZipSet::isUpToDate()
1662{
1663    const size_t N = mZipFile.size();
1664    for (size_t i=0; i<N; i++) {
1665        if (mZipFile[i] != NULL && !mZipFile[i]->isUpToDate()) {
1666            return false;
1667        }
1668    }
1669    return true;
1670}
1671
1672/*
1673 * Compute the zip file's index.
1674 *
1675 * "appName", "locale", and "vendor" should be set to NULL to indicate the
1676 * default directory.
1677 */
1678int AssetManager::ZipSet::getIndex(const String8& zip) const
1679{
1680    const size_t N = mZipPath.size();
1681    for (size_t i=0; i<N; i++) {
1682        if (mZipPath[i] == zip) {
1683            return i;
1684        }
1685    }
1686
1687    mZipPath.add(zip);
1688    mZipFile.add(NULL);
1689
1690    return mZipPath.size()-1;
1691}
1692
1693