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