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