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