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