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