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