AssetManager.cpp revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 * Scan the contents of the specified directory and merge them into the
905 * "pMergedInfo" vector, removing previous entries if we find "exclude"
906 * directives.
907 *
908 * Returns "false" if we found nothing to contribute.
909 */
910bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
911    const asset_path& ap, const char* rootDir, const char* dirName)
912{
913    SortedVector<AssetDir::FileInfo>* pContents;
914    String8 path;
915
916    assert(pMergedInfo != NULL);
917
918    //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName);
919
920    if (mCacheValid) {
921        int i, start, count;
922
923        pContents = new SortedVector<AssetDir::FileInfo>;
924
925        /*
926         * Get the basic partial path and find it in the cache.  That's
927         * the start point for the search.
928         */
929        path = createPathNameLocked(ap, rootDir);
930        if (dirName[0] != '\0')
931            path.appendPath(dirName);
932
933        start = mCache.indexOf(path);
934        if (start == NAME_NOT_FOUND) {
935            //printf("+++ not found in cache: dir '%s'\n", (const char*) path);
936            delete pContents;
937            return false;
938        }
939
940        /*
941         * The match string looks like "common/default/default/foo/bar/".
942         * The '/' on the end ensures that we don't match on the directory
943         * itself or on ".../foo/barfy/".
944         */
945        path.append("/");
946
947        count = mCache.size();
948
949        /*
950         * Pick out the stuff in the current dir by examining the pathname.
951         * It needs to match the partial pathname prefix, and not have a '/'
952         * (fssep) anywhere after the prefix.
953         */
954        for (i = start+1; i < count; i++) {
955            if (mCache[i].getFileName().length() > path.length() &&
956                strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0)
957            {
958                const char* name = mCache[i].getFileName().string();
959                // XXX THIS IS BROKEN!  Looks like we need to store the full
960                // path prefix separately from the file path.
961                if (strchr(name + path.length(), '/') == NULL) {
962                    /* grab it, reducing path to just the filename component */
963                    AssetDir::FileInfo tmp = mCache[i];
964                    tmp.setFileName(tmp.getFileName().getPathLeaf());
965                    pContents->add(tmp);
966                }
967            } else {
968                /* no longer in the dir or its subdirs */
969                break;
970            }
971
972        }
973    } else {
974        path = createPathNameLocked(ap, rootDir);
975        if (dirName[0] != '\0')
976            path.appendPath(dirName);
977        pContents = scanDirLocked(path);
978        if (pContents == NULL)
979            return false;
980    }
981
982    // if we wanted to do an incremental cache fill, we would do it here
983
984    /*
985     * Process "exclude" directives.  If we find a filename that ends with
986     * ".EXCLUDE", we look for a matching entry in the "merged" set, and
987     * remove it if we find it.  We also delete the "exclude" entry.
988     */
989    int i, count, exclExtLen;
990
991    count = pContents->size();
992    exclExtLen = strlen(kExcludeExtension);
993    for (i = 0; i < count; i++) {
994        const char* name;
995        int nameLen;
996
997        name = pContents->itemAt(i).getFileName().string();
998        nameLen = strlen(name);
999        if (nameLen > exclExtLen &&
1000            strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0)
1001        {
1002            String8 match(name, nameLen - exclExtLen);
1003            int matchIdx;
1004
1005            matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match);
1006            if (matchIdx > 0) {
1007                LOGV("Excluding '%s' [%s]\n",
1008                    pMergedInfo->itemAt(matchIdx).getFileName().string(),
1009                    pMergedInfo->itemAt(matchIdx).getSourceName().string());
1010                pMergedInfo->removeAt(matchIdx);
1011            } else {
1012                //printf("+++ no match on '%s'\n", (const char*) match);
1013            }
1014
1015            LOGD("HEY: size=%d removing %d\n", (int)pContents->size(), i);
1016            pContents->removeAt(i);
1017            i--;        // adjust "for" loop
1018            count--;    //  and loop limit
1019        }
1020    }
1021
1022    mergeInfoLocked(pMergedInfo, pContents);
1023
1024    delete pContents;
1025
1026    return true;
1027}
1028
1029/*
1030 * Scan the contents of the specified directory, and stuff what we find
1031 * into a newly-allocated vector.
1032 *
1033 * Files ending in ".gz" will have their extensions removed.
1034 *
1035 * We should probably think about skipping files with "illegal" names,
1036 * e.g. illegal characters (/\:) or excessive length.
1037 *
1038 * Returns NULL if the specified directory doesn't exist.
1039 */
1040SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& path)
1041{
1042    SortedVector<AssetDir::FileInfo>* pContents = NULL;
1043    DIR* dir;
1044    struct dirent* entry;
1045    FileType fileType;
1046
1047    LOGV("Scanning dir '%s'\n", path.string());
1048
1049    dir = opendir(path.string());
1050    if (dir == NULL)
1051        return NULL;
1052
1053    pContents = new SortedVector<AssetDir::FileInfo>;
1054
1055    while (1) {
1056        entry = readdir(dir);
1057        if (entry == NULL)
1058            break;
1059
1060        if (strcmp(entry->d_name, ".") == 0 ||
1061            strcmp(entry->d_name, "..") == 0)
1062            continue;
1063
1064#ifdef _DIRENT_HAVE_D_TYPE
1065        if (entry->d_type == DT_REG)
1066            fileType = kFileTypeRegular;
1067        else if (entry->d_type == DT_DIR)
1068            fileType = kFileTypeDirectory;
1069        else
1070            fileType = kFileTypeUnknown;
1071#else
1072        // stat the file
1073        fileType = ::getFileType(path.appendPathCopy(entry->d_name).string());
1074#endif
1075
1076        if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory)
1077            continue;
1078
1079        AssetDir::FileInfo info;
1080        info.set(String8(entry->d_name), fileType);
1081        if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0)
1082            info.setFileName(info.getFileName().getBasePath());
1083        info.setSourceName(path.appendPathCopy(info.getFileName()));
1084        pContents->add(info);
1085    }
1086
1087    closedir(dir);
1088    return pContents;
1089}
1090
1091/*
1092 * Scan the contents out of the specified Zip archive, and merge what we
1093 * find into "pMergedInfo".  If the Zip archive in question doesn't exist,
1094 * we return immediately.
1095 *
1096 * Returns "false" if we found nothing to contribute.
1097 */
1098bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1099    const asset_path& ap, const char* rootDir, const char* baseDirName)
1100{
1101    ZipFileRO* pZip;
1102    Vector<String8> dirs;
1103    AssetDir::FileInfo info;
1104    SortedVector<AssetDir::FileInfo> contents;
1105    String8 sourceName, zipName, dirName;
1106
1107    pZip = mZipSet.getZip(ap.path);
1108    if (pZip == NULL) {
1109        LOGW("Failure opening zip %s\n", ap.path.string());
1110        return false;
1111    }
1112
1113    zipName = ZipSet::getPathName(ap.path.string());
1114
1115    /* convert "sounds" to "rootDir/sounds" */
1116    if (rootDir != NULL) dirName = rootDir;
1117    dirName.appendPath(baseDirName);
1118
1119    /*
1120     * Scan through the list of files, looking for a match.  The files in
1121     * the Zip table of contents are not in sorted order, so we have to
1122     * process the entire list.  We're looking for a string that begins
1123     * with the characters in "dirName", is followed by a '/', and has no
1124     * subsequent '/' in the stuff that follows.
1125     *
1126     * What makes this especially fun is that directories are not stored
1127     * explicitly in Zip archives, so we have to infer them from context.
1128     * When we see "sounds/foo.wav" we have to leave a note to ourselves
1129     * to insert a directory called "sounds" into the list.  We store
1130     * these in temporary vector so that we only return each one once.
1131     *
1132     * Name comparisons are case-sensitive to match UNIX filesystem
1133     * semantics.
1134     */
1135    int dirNameLen = dirName.length();
1136    for (int i = 0; i < pZip->getNumEntries(); i++) {
1137        ZipEntryRO entry;
1138        char nameBuf[256];
1139
1140        entry = pZip->findEntryByIndex(i);
1141        if (pZip->getEntryFileName(entry, nameBuf, sizeof(nameBuf)) != 0) {
1142            // TODO: fix this if we expect to have long names
1143            LOGE("ARGH: name too long?\n");
1144            continue;
1145        }
1146        if (dirNameLen == 0 ||
1147            (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 &&
1148             nameBuf[dirNameLen] == '/'))
1149        {
1150            const char* cp;
1151            const char* nextSlash;
1152
1153            cp = nameBuf + dirNameLen;
1154            if (dirNameLen != 0)
1155                cp++;       // advance past the '/'
1156
1157            nextSlash = strchr(cp, '/');
1158//xxx this may break if there are bare directory entries
1159            if (nextSlash == NULL) {
1160                /* this is a file in the requested directory */
1161
1162                info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular);
1163
1164                info.setSourceName(
1165                    createZipSourceNameLocked(zipName, dirName, info.getFileName()));
1166
1167                contents.add(info);
1168                //printf("FOUND: file '%s'\n", (const char*) info.mFileName);
1169            } else {
1170                /* this is a subdir; add it if we don't already have it*/
1171                String8 subdirName(cp, nextSlash - cp);
1172                size_t j;
1173                size_t N = dirs.size();
1174
1175                for (j = 0; j < N; j++) {
1176                    if (subdirName == dirs[j]) {
1177                        break;
1178                    }
1179                }
1180                if (j == N) {
1181                    dirs.add(subdirName);
1182                }
1183
1184                //printf("FOUND: dir '%s'\n", (const char*) subdirName);
1185            }
1186        }
1187    }
1188
1189    /*
1190     * Add the set of unique directories.
1191     */
1192    for (int i = 0; i < (int) dirs.size(); i++) {
1193        info.set(dirs[i], kFileTypeDirectory);
1194        info.setSourceName(
1195            createZipSourceNameLocked(zipName, dirName, info.getFileName()));
1196        contents.add(info);
1197    }
1198
1199    mergeInfoLocked(pMergedInfo, &contents);
1200
1201    return true;
1202}
1203
1204
1205/*
1206 * Merge two vectors of FileInfo.
1207 *
1208 * The merged contents will be stuffed into *pMergedInfo.
1209 *
1210 * If an entry for a file exists in both "pMergedInfo" and "pContents",
1211 * we use the newer "pContents" entry.
1212 */
1213void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1214    const SortedVector<AssetDir::FileInfo>* pContents)
1215{
1216    /*
1217     * Merge what we found in this directory with what we found in
1218     * other places.
1219     *
1220     * Two basic approaches:
1221     * (1) Create a new array that holds the unique values of the two
1222     *     arrays.
1223     * (2) Take the elements from pContents and shove them into pMergedInfo.
1224     *
1225     * Because these are vectors of complex objects, moving elements around
1226     * inside the vector requires constructing new objects and allocating
1227     * storage for members.  With approach #1, we're always adding to the
1228     * end, whereas with #2 we could be inserting multiple elements at the
1229     * front of the vector.  Approach #1 requires a full copy of the
1230     * contents of pMergedInfo, but approach #2 requires the same copy for
1231     * every insertion at the front of pMergedInfo.
1232     *
1233     * (We should probably use a SortedVector interface that allows us to
1234     * just stuff items in, trusting us to maintain the sort order.)
1235     */
1236    SortedVector<AssetDir::FileInfo>* pNewSorted;
1237    int mergeMax, contMax;
1238    int mergeIdx, contIdx;
1239
1240    pNewSorted = new SortedVector<AssetDir::FileInfo>;
1241    mergeMax = pMergedInfo->size();
1242    contMax = pContents->size();
1243    mergeIdx = contIdx = 0;
1244
1245    while (mergeIdx < mergeMax || contIdx < contMax) {
1246        if (mergeIdx == mergeMax) {
1247            /* hit end of "merge" list, copy rest of "contents" */
1248            pNewSorted->add(pContents->itemAt(contIdx));
1249            contIdx++;
1250        } else if (contIdx == contMax) {
1251            /* hit end of "cont" list, copy rest of "merge" */
1252            pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
1253            mergeIdx++;
1254        } else if (pMergedInfo->itemAt(mergeIdx) == pContents->itemAt(contIdx))
1255        {
1256            /* items are identical, add newer and advance both indices */
1257            pNewSorted->add(pContents->itemAt(contIdx));
1258            mergeIdx++;
1259            contIdx++;
1260        } else if (pMergedInfo->itemAt(mergeIdx) < pContents->itemAt(contIdx))
1261        {
1262            /* "merge" is lower, add that one */
1263            pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
1264            mergeIdx++;
1265        } else {
1266            /* "cont" is lower, add that one */
1267            assert(pContents->itemAt(contIdx) < pMergedInfo->itemAt(mergeIdx));
1268            pNewSorted->add(pContents->itemAt(contIdx));
1269            contIdx++;
1270        }
1271    }
1272
1273    /*
1274     * Overwrite the "merged" list with the new stuff.
1275     */
1276    *pMergedInfo = *pNewSorted;
1277    delete pNewSorted;
1278
1279#if 0       // for Vector, rather than SortedVector
1280    int i, j;
1281    for (i = pContents->size() -1; i >= 0; i--) {
1282        bool add = true;
1283
1284        for (j = pMergedInfo->size() -1; j >= 0; j--) {
1285            /* case-sensitive comparisons, to behave like UNIX fs */
1286            if (strcmp(pContents->itemAt(i).mFileName,
1287                       pMergedInfo->itemAt(j).mFileName) == 0)
1288            {
1289                /* match, don't add this entry */
1290                add = false;
1291                break;
1292            }
1293        }
1294
1295        if (add)
1296            pMergedInfo->add(pContents->itemAt(i));
1297    }
1298#endif
1299}
1300
1301
1302/*
1303 * Load all files into the file name cache.  We want to do this across
1304 * all combinations of { appname, locale, vendor }, performing a recursive
1305 * directory traversal.
1306 *
1307 * This is not the most efficient data structure.  Also, gathering the
1308 * information as we needed it (file-by-file or directory-by-directory)
1309 * would be faster.  However, on the actual device, 99% of the files will
1310 * live in Zip archives, so this list will be very small.  The trouble
1311 * is that we have to check the "loose" files first, so it's important
1312 * that we don't beat the filesystem silly looking for files that aren't
1313 * there.
1314 *
1315 * Note on thread safety: this is the only function that causes updates
1316 * to mCache, and anybody who tries to use it will call here if !mCacheValid,
1317 * so we need to employ a mutex here.
1318 */
1319void AssetManager::loadFileNameCacheLocked(void)
1320{
1321    assert(!mCacheValid);
1322    assert(mCache.size() == 0);
1323
1324#ifdef DO_TIMINGS   // need to link against -lrt for this now
1325    DurationTimer timer;
1326    timer.start();
1327#endif
1328
1329    fncScanLocked(&mCache, "");
1330
1331#ifdef DO_TIMINGS
1332    timer.stop();
1333    LOGD("Cache scan took %.3fms\n",
1334        timer.durationUsecs() / 1000.0);
1335#endif
1336
1337#if 0
1338    int i;
1339    printf("CACHED FILE LIST (%d entries):\n", mCache.size());
1340    for (i = 0; i < (int) mCache.size(); i++) {
1341        printf(" %d: (%d) '%s'\n", i,
1342            mCache.itemAt(i).getFileType(),
1343            (const char*) mCache.itemAt(i).getFileName());
1344    }
1345#endif
1346
1347    mCacheValid = true;
1348}
1349
1350/*
1351 * Scan up to 8 versions of the specified directory.
1352 */
1353void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1354    const char* dirName)
1355{
1356    size_t i = mAssetPaths.size();
1357    while (i > 0) {
1358        i--;
1359        const asset_path& ap = mAssetPaths.itemAt(i);
1360        fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName);
1361        if (mLocale != NULL)
1362            fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName);
1363        if (mVendor != NULL)
1364            fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName);
1365        if (mLocale != NULL && mVendor != NULL)
1366            fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName);
1367    }
1368}
1369
1370/*
1371 * Recursively scan this directory and all subdirs.
1372 *
1373 * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE
1374 * files, and we prepend the extended partial path to the filenames.
1375 */
1376bool AssetManager::fncScanAndMergeDirLocked(
1377    SortedVector<AssetDir::FileInfo>* pMergedInfo,
1378    const asset_path& ap, const char* locale, const char* vendor,
1379    const char* dirName)
1380{
1381    SortedVector<AssetDir::FileInfo>* pContents;
1382    String8 partialPath;
1383    String8 fullPath;
1384
1385    // XXX This is broken -- the filename cache needs to hold the base
1386    // asset path separately from its filename.
1387
1388    partialPath = createPathNameLocked(ap, locale, vendor);
1389    if (dirName[0] != '\0') {
1390        partialPath.appendPath(dirName);
1391    }
1392
1393    fullPath = partialPath;
1394    pContents = scanDirLocked(fullPath);
1395    if (pContents == NULL) {
1396        return false;       // directory did not exist
1397    }
1398
1399    /*
1400     * Scan all subdirectories of the current dir, merging what we find
1401     * into "pMergedInfo".
1402     */
1403    for (int i = 0; i < (int) pContents->size(); i++) {
1404        if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) {
1405            String8 subdir(dirName);
1406            subdir.appendPath(pContents->itemAt(i).getFileName());
1407
1408            fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string());
1409        }
1410    }
1411
1412    /*
1413     * To be consistent, we want entries for the root directory.  If
1414     * we're the root, add one now.
1415     */
1416    if (dirName[0] == '\0') {
1417        AssetDir::FileInfo tmpInfo;
1418
1419        tmpInfo.set(String8(""), kFileTypeDirectory);
1420        tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor));
1421        pContents->add(tmpInfo);
1422    }
1423
1424    /*
1425     * We want to prepend the extended partial path to every entry in
1426     * "pContents".  It's the same value for each entry, so this will
1427     * not change the sorting order of the vector contents.
1428     */
1429    for (int i = 0; i < (int) pContents->size(); i++) {
1430        const AssetDir::FileInfo& info = pContents->itemAt(i);
1431        pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName()));
1432    }
1433
1434    mergeInfoLocked(pMergedInfo, pContents);
1435    return true;
1436}
1437
1438/*
1439 * Trash the cache.
1440 */
1441void AssetManager::purgeFileNameCacheLocked(void)
1442{
1443    mCacheValid = false;
1444    mCache.clear();
1445}
1446
1447/*
1448 * ===========================================================================
1449 *      AssetManager::SharedZip
1450 * ===========================================================================
1451 */
1452
1453
1454Mutex AssetManager::SharedZip::gLock;
1455DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen;
1456
1457AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
1458    : mPath(path), mZipFile(NULL), mModWhen(modWhen), mResourceTableAsset(NULL)
1459{
1460    //LOGI("Creating SharedZip %p %s\n", this, (const char*)mPath);
1461    mZipFile = new ZipFileRO;
1462    LOGV("+++ opening zip '%s'\n", mPath.string());
1463    if (mZipFile->open(mPath.string()) != NO_ERROR) {
1464        LOGD("failed to open Zip archive '%s'\n", mPath.string());
1465        delete mZipFile;
1466        mZipFile = NULL;
1467    }
1468}
1469
1470sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path)
1471{
1472    AutoMutex _l(gLock);
1473    time_t modWhen = getFileModDate(path);
1474    sp<SharedZip> zip = gOpen.valueFor(path).promote();
1475    if (zip != NULL && zip->mModWhen == modWhen) {
1476        return zip;
1477    }
1478    zip = new SharedZip(path, modWhen);
1479    gOpen.add(path, zip);
1480    return zip;
1481
1482}
1483
1484ZipFileRO* AssetManager::SharedZip::getZip()
1485{
1486    return mZipFile;
1487}
1488
1489Asset* AssetManager::SharedZip::getResourceTableAsset()
1490{
1491    LOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
1492    return mResourceTableAsset;
1493}
1494
1495Asset* AssetManager::SharedZip::setResourceTableAsset(Asset* asset)
1496{
1497    {
1498        AutoMutex _l(gLock);
1499        if (mResourceTableAsset == NULL) {
1500            mResourceTableAsset = asset;
1501            // This is not thread safe the first time it is called, so
1502            // do it here with the global lock held.
1503            asset->getBuffer(true);
1504            return asset;
1505        }
1506    }
1507    delete asset;
1508    return mResourceTableAsset;
1509}
1510
1511bool AssetManager::SharedZip::isUpToDate()
1512{
1513    time_t modWhen = getFileModDate(mPath.string());
1514    return mModWhen == modWhen;
1515}
1516
1517AssetManager::SharedZip::~SharedZip()
1518{
1519    //LOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath);
1520    if (mResourceTableAsset != NULL) {
1521        delete mResourceTableAsset;
1522    }
1523    if (mZipFile != NULL) {
1524        delete mZipFile;
1525        LOGV("Closed '%s'\n", mPath.string());
1526    }
1527}
1528
1529/*
1530 * ===========================================================================
1531 *      AssetManager::ZipSet
1532 * ===========================================================================
1533 */
1534
1535/*
1536 * Constructor.
1537 */
1538AssetManager::ZipSet::ZipSet(void)
1539{
1540}
1541
1542/*
1543 * Destructor.  Close any open archives.
1544 */
1545AssetManager::ZipSet::~ZipSet(void)
1546{
1547    size_t N = mZipFile.size();
1548    for (size_t i = 0; i < N; i++)
1549        closeZip(i);
1550}
1551
1552/*
1553 * Close a Zip file and reset the entry.
1554 */
1555void AssetManager::ZipSet::closeZip(int idx)
1556{
1557    mZipFile.editItemAt(idx) = NULL;
1558}
1559
1560
1561/*
1562 * Retrieve the appropriate Zip file from the set.
1563 */
1564ZipFileRO* AssetManager::ZipSet::getZip(const String8& path)
1565{
1566    int idx = getIndex(path);
1567    sp<SharedZip> zip = mZipFile[idx];
1568    if (zip == NULL) {
1569        zip = SharedZip::get(path);
1570        mZipFile.editItemAt(idx) = zip;
1571    }
1572    return zip->getZip();
1573}
1574
1575Asset* AssetManager::ZipSet::getZipResourceTable(const String8& path)
1576{
1577    int idx = getIndex(path);
1578    sp<SharedZip> zip = mZipFile[idx];
1579    if (zip == NULL) {
1580        zip = SharedZip::get(path);
1581        mZipFile.editItemAt(idx) = zip;
1582    }
1583    return zip->getResourceTableAsset();
1584}
1585
1586Asset* AssetManager::ZipSet::setZipResourceTable(const String8& path,
1587                                                 Asset* asset)
1588{
1589    int idx = getIndex(path);
1590    sp<SharedZip> zip = mZipFile[idx];
1591    // doesn't make sense to call before previously accessing.
1592    return zip->setResourceTableAsset(asset);
1593}
1594
1595/*
1596 * Generate the partial pathname for the specified archive.  The caller
1597 * gets to prepend the asset root directory.
1598 *
1599 * Returns something like "common/en-US-noogle.jar".
1600 */
1601/*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath)
1602{
1603    return String8(zipPath);
1604}
1605
1606bool AssetManager::ZipSet::isUpToDate()
1607{
1608    const size_t N = mZipFile.size();
1609    for (size_t i=0; i<N; i++) {
1610        if (mZipFile[i] != NULL && !mZipFile[i]->isUpToDate()) {
1611            return false;
1612        }
1613    }
1614    return true;
1615}
1616
1617/*
1618 * Compute the zip file's index.
1619 *
1620 * "appName", "locale", and "vendor" should be set to NULL to indicate the
1621 * default directory.
1622 */
1623int AssetManager::ZipSet::getIndex(const String8& zip) const
1624{
1625    const size_t N = mZipPath.size();
1626    for (size_t i=0; i<N; i++) {
1627        if (mZipPath[i] == zip) {
1628            return i;
1629        }
1630    }
1631
1632    mZipPath.add(zip);
1633    mZipFile.add(NULL);
1634
1635    return mZipPath.size()-1;
1636}
1637
1638