blob: 5a05e6a61f1778acb0f1c80ac9c2d1136e067f7f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
40using 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 */
46static const char* kDefaultLocale = "default";
47static const char* kDefaultVendor = "default";
48static const char* kAssetsRoot = "assets";
49static const char* kAppZipName = NULL; //"classes.jar";
50static const char* kSystemAssets = "framework/framework-res.apk";
51
52static const char* kExcludeExtension = ".EXCLUDE";
53
54static Asset* const kExcludedAsset = (Asset*) 0xd000000d;
55
56static volatile int32_t gCount = 0;
57
58
59/*
60 * ===========================================================================
61 * AssetManager
62 * ===========================================================================
63 */
64
65int32_t AssetManager::getGlobalCount()
66{
67 return gCount;
68}
69
70AssetManager::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
80AssetManager::~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
93bool 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
139bool 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
150void* 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
157String8 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 */
173void AssetManager::setLocale(const char* locale)
174{
175 AutoMutex _l(mLock);
176 setLocaleLocked(locale);
177}
178
179void 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 */
198void 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
211void 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 */
259Asset* 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 */
296Asset* 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
324Asset* 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 */
355FileType 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
373const 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;
Dianne Hackborn78c40512009-07-06 11:07:40 -0700398 ResTable* sharedRes = NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399 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) {
Dianne Hackborn78c40512009-07-06 11:07:40 -0700403 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) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 ass = const_cast<AssetManager*>(this)->
Dianne Hackborn78c40512009-07-06 11:07:40 -0700411 mZipSet.getZipResourceTableAsset(ap.path);
412 if (ass == NULL) {
413 LOGV("loading resource table %s\n", ap.path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414 ass = const_cast<AssetManager*>(this)->
Dianne Hackborn78c40512009-07-06 11:07:40 -0700415 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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 }
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 }
Dianne Hackborn78c40512009-07-06 11:07:40 -0700443 if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800444 if (rt == NULL) {
445 mResources = rt = new ResTable();
446 updateResourceParamsLocked();
447 }
448 LOGV("Installing resource asset %p in to table %p\n", ass, mResources);
Dianne Hackborn78c40512009-07-06 11:07:40 -0700449 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456
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
470void 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
495const ResTable& AssetManager::getResources(bool required) const
496{
497 const ResTable* rt = getResTable(required);
498 return *rt;
499}
500
501bool AssetManager::isUpToDate()
502{
503 AutoMutex _l(mLock);
504 return mZipSet.isUpToDate();
505}
506
507void 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 */
522Asset* 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 */
581Asset* 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 */
610Asset* 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 */
731String8 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 */
747String8 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 */
759String8 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 */
779ZipFileRO* 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 */
798Asset* 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 */
820Asset* 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 */
875AssetDir* 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/*
Dianne Hackbornbb9ea302009-05-18 15:22:00 -0700930 * 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 */
938AssetDir* 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/*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800984 * 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 */
990bool 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 */
1120SortedVector<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 */
1178bool 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 }
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001226 //printf("Comparing %s in %s?\n", nameBuf, dirName.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001227 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);
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001249 //printf("FOUND: file '%s'\n", info.getFileName().string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 } 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
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001265 //printf("FOUND: dir '%s'\n", subdirName.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001266 }
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 */
1294void 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 */
1400void 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 */
1434void 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 */
1457bool 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 */
1522void AssetManager::purgeFileNameCacheLocked(void)
1523{
1524 mCacheValid = false;
1525 mCache.clear();
1526}
1527
1528/*
1529 * ===========================================================================
1530 * AssetManager::SharedZip
1531 * ===========================================================================
1532 */
1533
1534
1535Mutex AssetManager::SharedZip::gLock;
1536DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen;
1537
1538AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
Dianne Hackborn78c40512009-07-06 11:07:40 -07001539 : mPath(path), mZipFile(NULL), mModWhen(modWhen),
1540 mResourceTableAsset(NULL), mResourceTable(NULL)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001541{
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
1552sp<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
1566ZipFileRO* AssetManager::SharedZip::getZip()
1567{
1568 return mZipFile;
1569}
1570
1571Asset* AssetManager::SharedZip::getResourceTableAsset()
1572{
1573 LOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
1574 return mResourceTableAsset;
1575}
1576
1577Asset* 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
Dianne Hackborn78c40512009-07-06 11:07:40 -07001593ResTable* AssetManager::SharedZip::getResourceTable()
1594{
1595 LOGV("Getting from SharedZip %p resource table %p\n", this, mResourceTable);
1596 return mResourceTable;
1597}
1598
1599ResTable* 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001612bool AssetManager::SharedZip::isUpToDate()
1613{
1614 time_t modWhen = getFileModDate(mPath.string());
1615 return mModWhen == modWhen;
1616}
1617
1618AssetManager::SharedZip::~SharedZip()
1619{
1620 //LOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath);
Dianne Hackborn78c40512009-07-06 11:07:40 -07001621 if (mResourceTable != NULL) {
1622 delete mResourceTable;
1623 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001624 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 */
1642AssetManager::ZipSet::ZipSet(void)
1643{
1644}
1645
1646/*
1647 * Destructor. Close any open archives.
1648 */
1649AssetManager::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 */
1659void 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 */
1668ZipFileRO* 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
Dianne Hackborn78c40512009-07-06 11:07:40 -07001679Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001680{
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
Dianne Hackborn78c40512009-07-06 11:07:40 -07001690Asset* AssetManager::ZipSet::setZipResourceTableAsset(const String8& path,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001691 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
Dianne Hackborn78c40512009-07-06 11:07:40 -07001699ResTable* 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
1710ResTable* 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001719/*
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 */
1725/*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath)
1726{
1727 return String8(zipPath);
1728}
1729
1730bool 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 */
1747int 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