blob: 4126bfb7a984b4ebb58434257962e2292ea247e3 [file] [log] [blame]
The Android Open Source Projectedbf3b62009-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;
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/*
Dianne Hackborn7a579852009-05-18 15:22:00 -0700904 * Open a directory in the non-asset namespace.
905 *
906 * An "asset directory" is simply the combination of all files in all
907 * locations, with ".gz" stripped for loose files. With app, locale, and
908 * vendor defined, we have 8 directories and 2 Zip archives to scan.
909 *
910 * Pass in "" for the root dir.
911 */
912AssetDir* AssetManager::openNonAssetDir(void* cookie, const char* dirName)
913{
914 AutoMutex _l(mLock);
915
916 AssetDir* pDir = NULL;
917 SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL;
918
919 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
920 assert(dirName != NULL);
921
922 //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
923
924 if (mCacheMode != CACHE_OFF && !mCacheValid)
925 loadFileNameCacheLocked();
926
927 pDir = new AssetDir;
928
929 pMergedInfo = new SortedVector<AssetDir::FileInfo>;
930
931 const size_t which = ((size_t)cookie)-1;
932
933 if (which < mAssetPaths.size()) {
934 const asset_path& ap = mAssetPaths.itemAt(which);
935 if (ap.type == kFileTypeRegular) {
936 LOGV("Adding directory %s from zip %s", dirName, ap.path.string());
937 scanAndMergeZipLocked(pMergedInfo, ap, NULL, dirName);
938 } else {
939 LOGV("Adding directory %s from dir %s", dirName, ap.path.string());
940 scanAndMergeDirLocked(pMergedInfo, ap, NULL, dirName);
941 }
942 }
943
944#if 0
945 printf("FILE LIST:\n");
946 for (i = 0; i < (size_t) pMergedInfo->size(); i++) {
947 printf(" %d: (%d) '%s'\n", i,
948 pMergedInfo->itemAt(i).getFileType(),
949 (const char*) pMergedInfo->itemAt(i).getFileName());
950 }
951#endif
952
953 pDir->setFileList(pMergedInfo);
954 return pDir;
955}
956
957/*
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800958 * Scan the contents of the specified directory and merge them into the
959 * "pMergedInfo" vector, removing previous entries if we find "exclude"
960 * directives.
961 *
962 * Returns "false" if we found nothing to contribute.
963 */
964bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
965 const asset_path& ap, const char* rootDir, const char* dirName)
966{
967 SortedVector<AssetDir::FileInfo>* pContents;
968 String8 path;
969
970 assert(pMergedInfo != NULL);
971
972 //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName);
973
974 if (mCacheValid) {
975 int i, start, count;
976
977 pContents = new SortedVector<AssetDir::FileInfo>;
978
979 /*
980 * Get the basic partial path and find it in the cache. That's
981 * the start point for the search.
982 */
983 path = createPathNameLocked(ap, rootDir);
984 if (dirName[0] != '\0')
985 path.appendPath(dirName);
986
987 start = mCache.indexOf(path);
988 if (start == NAME_NOT_FOUND) {
989 //printf("+++ not found in cache: dir '%s'\n", (const char*) path);
990 delete pContents;
991 return false;
992 }
993
994 /*
995 * The match string looks like "common/default/default/foo/bar/".
996 * The '/' on the end ensures that we don't match on the directory
997 * itself or on ".../foo/barfy/".
998 */
999 path.append("/");
1000
1001 count = mCache.size();
1002
1003 /*
1004 * Pick out the stuff in the current dir by examining the pathname.
1005 * It needs to match the partial pathname prefix, and not have a '/'
1006 * (fssep) anywhere after the prefix.
1007 */
1008 for (i = start+1; i < count; i++) {
1009 if (mCache[i].getFileName().length() > path.length() &&
1010 strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0)
1011 {
1012 const char* name = mCache[i].getFileName().string();
1013 // XXX THIS IS BROKEN! Looks like we need to store the full
1014 // path prefix separately from the file path.
1015 if (strchr(name + path.length(), '/') == NULL) {
1016 /* grab it, reducing path to just the filename component */
1017 AssetDir::FileInfo tmp = mCache[i];
1018 tmp.setFileName(tmp.getFileName().getPathLeaf());
1019 pContents->add(tmp);
1020 }
1021 } else {
1022 /* no longer in the dir or its subdirs */
1023 break;
1024 }
1025
1026 }
1027 } else {
1028 path = createPathNameLocked(ap, rootDir);
1029 if (dirName[0] != '\0')
1030 path.appendPath(dirName);
1031 pContents = scanDirLocked(path);
1032 if (pContents == NULL)
1033 return false;
1034 }
1035
1036 // if we wanted to do an incremental cache fill, we would do it here
1037
1038 /*
1039 * Process "exclude" directives. If we find a filename that ends with
1040 * ".EXCLUDE", we look for a matching entry in the "merged" set, and
1041 * remove it if we find it. We also delete the "exclude" entry.
1042 */
1043 int i, count, exclExtLen;
1044
1045 count = pContents->size();
1046 exclExtLen = strlen(kExcludeExtension);
1047 for (i = 0; i < count; i++) {
1048 const char* name;
1049 int nameLen;
1050
1051 name = pContents->itemAt(i).getFileName().string();
1052 nameLen = strlen(name);
1053 if (nameLen > exclExtLen &&
1054 strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0)
1055 {
1056 String8 match(name, nameLen - exclExtLen);
1057 int matchIdx;
1058
1059 matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match);
1060 if (matchIdx > 0) {
1061 LOGV("Excluding '%s' [%s]\n",
1062 pMergedInfo->itemAt(matchIdx).getFileName().string(),
1063 pMergedInfo->itemAt(matchIdx).getSourceName().string());
1064 pMergedInfo->removeAt(matchIdx);
1065 } else {
1066 //printf("+++ no match on '%s'\n", (const char*) match);
1067 }
1068
1069 LOGD("HEY: size=%d removing %d\n", (int)pContents->size(), i);
1070 pContents->removeAt(i);
1071 i--; // adjust "for" loop
1072 count--; // and loop limit
1073 }
1074 }
1075
1076 mergeInfoLocked(pMergedInfo, pContents);
1077
1078 delete pContents;
1079
1080 return true;
1081}
1082
1083/*
1084 * Scan the contents of the specified directory, and stuff what we find
1085 * into a newly-allocated vector.
1086 *
1087 * Files ending in ".gz" will have their extensions removed.
1088 *
1089 * We should probably think about skipping files with "illegal" names,
1090 * e.g. illegal characters (/\:) or excessive length.
1091 *
1092 * Returns NULL if the specified directory doesn't exist.
1093 */
1094SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& path)
1095{
1096 SortedVector<AssetDir::FileInfo>* pContents = NULL;
1097 DIR* dir;
1098 struct dirent* entry;
1099 FileType fileType;
1100
1101 LOGV("Scanning dir '%s'\n", path.string());
1102
1103 dir = opendir(path.string());
1104 if (dir == NULL)
1105 return NULL;
1106
1107 pContents = new SortedVector<AssetDir::FileInfo>;
1108
1109 while (1) {
1110 entry = readdir(dir);
1111 if (entry == NULL)
1112 break;
1113
1114 if (strcmp(entry->d_name, ".") == 0 ||
1115 strcmp(entry->d_name, "..") == 0)
1116 continue;
1117
1118#ifdef _DIRENT_HAVE_D_TYPE
1119 if (entry->d_type == DT_REG)
1120 fileType = kFileTypeRegular;
1121 else if (entry->d_type == DT_DIR)
1122 fileType = kFileTypeDirectory;
1123 else
1124 fileType = kFileTypeUnknown;
1125#else
1126 // stat the file
1127 fileType = ::getFileType(path.appendPathCopy(entry->d_name).string());
1128#endif
1129
1130 if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory)
1131 continue;
1132
1133 AssetDir::FileInfo info;
1134 info.set(String8(entry->d_name), fileType);
1135 if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0)
1136 info.setFileName(info.getFileName().getBasePath());
1137 info.setSourceName(path.appendPathCopy(info.getFileName()));
1138 pContents->add(info);
1139 }
1140
1141 closedir(dir);
1142 return pContents;
1143}
1144
1145/*
1146 * Scan the contents out of the specified Zip archive, and merge what we
1147 * find into "pMergedInfo". If the Zip archive in question doesn't exist,
1148 * we return immediately.
1149 *
1150 * Returns "false" if we found nothing to contribute.
1151 */
1152bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1153 const asset_path& ap, const char* rootDir, const char* baseDirName)
1154{
1155 ZipFileRO* pZip;
1156 Vector<String8> dirs;
1157 AssetDir::FileInfo info;
1158 SortedVector<AssetDir::FileInfo> contents;
1159 String8 sourceName, zipName, dirName;
1160
1161 pZip = mZipSet.getZip(ap.path);
1162 if (pZip == NULL) {
1163 LOGW("Failure opening zip %s\n", ap.path.string());
1164 return false;
1165 }
1166
1167 zipName = ZipSet::getPathName(ap.path.string());
1168
1169 /* convert "sounds" to "rootDir/sounds" */
1170 if (rootDir != NULL) dirName = rootDir;
1171 dirName.appendPath(baseDirName);
1172
1173 /*
1174 * Scan through the list of files, looking for a match. The files in
1175 * the Zip table of contents are not in sorted order, so we have to
1176 * process the entire list. We're looking for a string that begins
1177 * with the characters in "dirName", is followed by a '/', and has no
1178 * subsequent '/' in the stuff that follows.
1179 *
1180 * What makes this especially fun is that directories are not stored
1181 * explicitly in Zip archives, so we have to infer them from context.
1182 * When we see "sounds/foo.wav" we have to leave a note to ourselves
1183 * to insert a directory called "sounds" into the list. We store
1184 * these in temporary vector so that we only return each one once.
1185 *
1186 * Name comparisons are case-sensitive to match UNIX filesystem
1187 * semantics.
1188 */
1189 int dirNameLen = dirName.length();
1190 for (int i = 0; i < pZip->getNumEntries(); i++) {
1191 ZipEntryRO entry;
1192 char nameBuf[256];
1193
1194 entry = pZip->findEntryByIndex(i);
1195 if (pZip->getEntryFileName(entry, nameBuf, sizeof(nameBuf)) != 0) {
1196 // TODO: fix this if we expect to have long names
1197 LOGE("ARGH: name too long?\n");
1198 continue;
1199 }
Dianne Hackborn7a579852009-05-18 15:22:00 -07001200 //printf("Comparing %s in %s?\n", nameBuf, dirName.string());
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -08001201 if (dirNameLen == 0 ||
1202 (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 &&
1203 nameBuf[dirNameLen] == '/'))
1204 {
1205 const char* cp;
1206 const char* nextSlash;
1207
1208 cp = nameBuf + dirNameLen;
1209 if (dirNameLen != 0)
1210 cp++; // advance past the '/'
1211
1212 nextSlash = strchr(cp, '/');
1213//xxx this may break if there are bare directory entries
1214 if (nextSlash == NULL) {
1215 /* this is a file in the requested directory */
1216
1217 info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular);
1218
1219 info.setSourceName(
1220 createZipSourceNameLocked(zipName, dirName, info.getFileName()));
1221
1222 contents.add(info);
Dianne Hackborn7a579852009-05-18 15:22:00 -07001223 //printf("FOUND: file '%s'\n", info.getFileName().string());
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -08001224 } else {
1225 /* this is a subdir; add it if we don't already have it*/
1226 String8 subdirName(cp, nextSlash - cp);
1227 size_t j;
1228 size_t N = dirs.size();
1229
1230 for (j = 0; j < N; j++) {
1231 if (subdirName == dirs[j]) {
1232 break;
1233 }
1234 }
1235 if (j == N) {
1236 dirs.add(subdirName);
1237 }
1238
Dianne Hackborn7a579852009-05-18 15:22:00 -07001239 //printf("FOUND: dir '%s'\n", subdirName.string());
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -08001240 }
1241 }
1242 }
1243
1244 /*
1245 * Add the set of unique directories.
1246 */
1247 for (int i = 0; i < (int) dirs.size(); i++) {
1248 info.set(dirs[i], kFileTypeDirectory);
1249 info.setSourceName(
1250 createZipSourceNameLocked(zipName, dirName, info.getFileName()));
1251 contents.add(info);
1252 }
1253
1254 mergeInfoLocked(pMergedInfo, &contents);
1255
1256 return true;
1257}
1258
1259
1260/*
1261 * Merge two vectors of FileInfo.
1262 *
1263 * The merged contents will be stuffed into *pMergedInfo.
1264 *
1265 * If an entry for a file exists in both "pMergedInfo" and "pContents",
1266 * we use the newer "pContents" entry.
1267 */
1268void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1269 const SortedVector<AssetDir::FileInfo>* pContents)
1270{
1271 /*
1272 * Merge what we found in this directory with what we found in
1273 * other places.
1274 *
1275 * Two basic approaches:
1276 * (1) Create a new array that holds the unique values of the two
1277 * arrays.
1278 * (2) Take the elements from pContents and shove them into pMergedInfo.
1279 *
1280 * Because these are vectors of complex objects, moving elements around
1281 * inside the vector requires constructing new objects and allocating
1282 * storage for members. With approach #1, we're always adding to the
1283 * end, whereas with #2 we could be inserting multiple elements at the
1284 * front of the vector. Approach #1 requires a full copy of the
1285 * contents of pMergedInfo, but approach #2 requires the same copy for
1286 * every insertion at the front of pMergedInfo.
1287 *
1288 * (We should probably use a SortedVector interface that allows us to
1289 * just stuff items in, trusting us to maintain the sort order.)
1290 */
1291 SortedVector<AssetDir::FileInfo>* pNewSorted;
1292 int mergeMax, contMax;
1293 int mergeIdx, contIdx;
1294
1295 pNewSorted = new SortedVector<AssetDir::FileInfo>;
1296 mergeMax = pMergedInfo->size();
1297 contMax = pContents->size();
1298 mergeIdx = contIdx = 0;
1299
1300 while (mergeIdx < mergeMax || contIdx < contMax) {
1301 if (mergeIdx == mergeMax) {
1302 /* hit end of "merge" list, copy rest of "contents" */
1303 pNewSorted->add(pContents->itemAt(contIdx));
1304 contIdx++;
1305 } else if (contIdx == contMax) {
1306 /* hit end of "cont" list, copy rest of "merge" */
1307 pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
1308 mergeIdx++;
1309 } else if (pMergedInfo->itemAt(mergeIdx) == pContents->itemAt(contIdx))
1310 {
1311 /* items are identical, add newer and advance both indices */
1312 pNewSorted->add(pContents->itemAt(contIdx));
1313 mergeIdx++;
1314 contIdx++;
1315 } else if (pMergedInfo->itemAt(mergeIdx) < pContents->itemAt(contIdx))
1316 {
1317 /* "merge" is lower, add that one */
1318 pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
1319 mergeIdx++;
1320 } else {
1321 /* "cont" is lower, add that one */
1322 assert(pContents->itemAt(contIdx) < pMergedInfo->itemAt(mergeIdx));
1323 pNewSorted->add(pContents->itemAt(contIdx));
1324 contIdx++;
1325 }
1326 }
1327
1328 /*
1329 * Overwrite the "merged" list with the new stuff.
1330 */
1331 *pMergedInfo = *pNewSorted;
1332 delete pNewSorted;
1333
1334#if 0 // for Vector, rather than SortedVector
1335 int i, j;
1336 for (i = pContents->size() -1; i >= 0; i--) {
1337 bool add = true;
1338
1339 for (j = pMergedInfo->size() -1; j >= 0; j--) {
1340 /* case-sensitive comparisons, to behave like UNIX fs */
1341 if (strcmp(pContents->itemAt(i).mFileName,
1342 pMergedInfo->itemAt(j).mFileName) == 0)
1343 {
1344 /* match, don't add this entry */
1345 add = false;
1346 break;
1347 }
1348 }
1349
1350 if (add)
1351 pMergedInfo->add(pContents->itemAt(i));
1352 }
1353#endif
1354}
1355
1356
1357/*
1358 * Load all files into the file name cache. We want to do this across
1359 * all combinations of { appname, locale, vendor }, performing a recursive
1360 * directory traversal.
1361 *
1362 * This is not the most efficient data structure. Also, gathering the
1363 * information as we needed it (file-by-file or directory-by-directory)
1364 * would be faster. However, on the actual device, 99% of the files will
1365 * live in Zip archives, so this list will be very small. The trouble
1366 * is that we have to check the "loose" files first, so it's important
1367 * that we don't beat the filesystem silly looking for files that aren't
1368 * there.
1369 *
1370 * Note on thread safety: this is the only function that causes updates
1371 * to mCache, and anybody who tries to use it will call here if !mCacheValid,
1372 * so we need to employ a mutex here.
1373 */
1374void AssetManager::loadFileNameCacheLocked(void)
1375{
1376 assert(!mCacheValid);
1377 assert(mCache.size() == 0);
1378
1379#ifdef DO_TIMINGS // need to link against -lrt for this now
1380 DurationTimer timer;
1381 timer.start();
1382#endif
1383
1384 fncScanLocked(&mCache, "");
1385
1386#ifdef DO_TIMINGS
1387 timer.stop();
1388 LOGD("Cache scan took %.3fms\n",
1389 timer.durationUsecs() / 1000.0);
1390#endif
1391
1392#if 0
1393 int i;
1394 printf("CACHED FILE LIST (%d entries):\n", mCache.size());
1395 for (i = 0; i < (int) mCache.size(); i++) {
1396 printf(" %d: (%d) '%s'\n", i,
1397 mCache.itemAt(i).getFileType(),
1398 (const char*) mCache.itemAt(i).getFileName());
1399 }
1400#endif
1401
1402 mCacheValid = true;
1403}
1404
1405/*
1406 * Scan up to 8 versions of the specified directory.
1407 */
1408void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1409 const char* dirName)
1410{
1411 size_t i = mAssetPaths.size();
1412 while (i > 0) {
1413 i--;
1414 const asset_path& ap = mAssetPaths.itemAt(i);
1415 fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName);
1416 if (mLocale != NULL)
1417 fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName);
1418 if (mVendor != NULL)
1419 fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName);
1420 if (mLocale != NULL && mVendor != NULL)
1421 fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName);
1422 }
1423}
1424
1425/*
1426 * Recursively scan this directory and all subdirs.
1427 *
1428 * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE
1429 * files, and we prepend the extended partial path to the filenames.
1430 */
1431bool AssetManager::fncScanAndMergeDirLocked(
1432 SortedVector<AssetDir::FileInfo>* pMergedInfo,
1433 const asset_path& ap, const char* locale, const char* vendor,
1434 const char* dirName)
1435{
1436 SortedVector<AssetDir::FileInfo>* pContents;
1437 String8 partialPath;
1438 String8 fullPath;
1439
1440 // XXX This is broken -- the filename cache needs to hold the base
1441 // asset path separately from its filename.
1442
1443 partialPath = createPathNameLocked(ap, locale, vendor);
1444 if (dirName[0] != '\0') {
1445 partialPath.appendPath(dirName);
1446 }
1447
1448 fullPath = partialPath;
1449 pContents = scanDirLocked(fullPath);
1450 if (pContents == NULL) {
1451 return false; // directory did not exist
1452 }
1453
1454 /*
1455 * Scan all subdirectories of the current dir, merging what we find
1456 * into "pMergedInfo".
1457 */
1458 for (int i = 0; i < (int) pContents->size(); i++) {
1459 if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) {
1460 String8 subdir(dirName);
1461 subdir.appendPath(pContents->itemAt(i).getFileName());
1462
1463 fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string());
1464 }
1465 }
1466
1467 /*
1468 * To be consistent, we want entries for the root directory. If
1469 * we're the root, add one now.
1470 */
1471 if (dirName[0] == '\0') {
1472 AssetDir::FileInfo tmpInfo;
1473
1474 tmpInfo.set(String8(""), kFileTypeDirectory);
1475 tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor));
1476 pContents->add(tmpInfo);
1477 }
1478
1479 /*
1480 * We want to prepend the extended partial path to every entry in
1481 * "pContents". It's the same value for each entry, so this will
1482 * not change the sorting order of the vector contents.
1483 */
1484 for (int i = 0; i < (int) pContents->size(); i++) {
1485 const AssetDir::FileInfo& info = pContents->itemAt(i);
1486 pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName()));
1487 }
1488
1489 mergeInfoLocked(pMergedInfo, pContents);
1490 return true;
1491}
1492
1493/*
1494 * Trash the cache.
1495 */
1496void AssetManager::purgeFileNameCacheLocked(void)
1497{
1498 mCacheValid = false;
1499 mCache.clear();
1500}
1501
1502/*
1503 * ===========================================================================
1504 * AssetManager::SharedZip
1505 * ===========================================================================
1506 */
1507
1508
1509Mutex AssetManager::SharedZip::gLock;
1510DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen;
1511
1512AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
1513 : mPath(path), mZipFile(NULL), mModWhen(modWhen), mResourceTableAsset(NULL)
1514{
1515 //LOGI("Creating SharedZip %p %s\n", this, (const char*)mPath);
1516 mZipFile = new ZipFileRO;
1517 LOGV("+++ opening zip '%s'\n", mPath.string());
1518 if (mZipFile->open(mPath.string()) != NO_ERROR) {
1519 LOGD("failed to open Zip archive '%s'\n", mPath.string());
1520 delete mZipFile;
1521 mZipFile = NULL;
1522 }
1523}
1524
1525sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path)
1526{
1527 AutoMutex _l(gLock);
1528 time_t modWhen = getFileModDate(path);
1529 sp<SharedZip> zip = gOpen.valueFor(path).promote();
1530 if (zip != NULL && zip->mModWhen == modWhen) {
1531 return zip;
1532 }
1533 zip = new SharedZip(path, modWhen);
1534 gOpen.add(path, zip);
1535 return zip;
1536
1537}
1538
1539ZipFileRO* AssetManager::SharedZip::getZip()
1540{
1541 return mZipFile;
1542}
1543
1544Asset* AssetManager::SharedZip::getResourceTableAsset()
1545{
1546 LOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
1547 return mResourceTableAsset;
1548}
1549
1550Asset* AssetManager::SharedZip::setResourceTableAsset(Asset* asset)
1551{
1552 {
1553 AutoMutex _l(gLock);
1554 if (mResourceTableAsset == NULL) {
1555 mResourceTableAsset = asset;
1556 // This is not thread safe the first time it is called, so
1557 // do it here with the global lock held.
1558 asset->getBuffer(true);
1559 return asset;
1560 }
1561 }
1562 delete asset;
1563 return mResourceTableAsset;
1564}
1565
1566bool AssetManager::SharedZip::isUpToDate()
1567{
1568 time_t modWhen = getFileModDate(mPath.string());
1569 return mModWhen == modWhen;
1570}
1571
1572AssetManager::SharedZip::~SharedZip()
1573{
1574 //LOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath);
1575 if (mResourceTableAsset != NULL) {
1576 delete mResourceTableAsset;
1577 }
1578 if (mZipFile != NULL) {
1579 delete mZipFile;
1580 LOGV("Closed '%s'\n", mPath.string());
1581 }
1582}
1583
1584/*
1585 * ===========================================================================
1586 * AssetManager::ZipSet
1587 * ===========================================================================
1588 */
1589
1590/*
1591 * Constructor.
1592 */
1593AssetManager::ZipSet::ZipSet(void)
1594{
1595}
1596
1597/*
1598 * Destructor. Close any open archives.
1599 */
1600AssetManager::ZipSet::~ZipSet(void)
1601{
1602 size_t N = mZipFile.size();
1603 for (size_t i = 0; i < N; i++)
1604 closeZip(i);
1605}
1606
1607/*
1608 * Close a Zip file and reset the entry.
1609 */
1610void AssetManager::ZipSet::closeZip(int idx)
1611{
1612 mZipFile.editItemAt(idx) = NULL;
1613}
1614
1615
1616/*
1617 * Retrieve the appropriate Zip file from the set.
1618 */
1619ZipFileRO* AssetManager::ZipSet::getZip(const String8& path)
1620{
1621 int idx = getIndex(path);
1622 sp<SharedZip> zip = mZipFile[idx];
1623 if (zip == NULL) {
1624 zip = SharedZip::get(path);
1625 mZipFile.editItemAt(idx) = zip;
1626 }
1627 return zip->getZip();
1628}
1629
1630Asset* AssetManager::ZipSet::getZipResourceTable(const String8& path)
1631{
1632 int idx = getIndex(path);
1633 sp<SharedZip> zip = mZipFile[idx];
1634 if (zip == NULL) {
1635 zip = SharedZip::get(path);
1636 mZipFile.editItemAt(idx) = zip;
1637 }
1638 return zip->getResourceTableAsset();
1639}
1640
1641Asset* AssetManager::ZipSet::setZipResourceTable(const String8& path,
1642 Asset* asset)
1643{
1644 int idx = getIndex(path);
1645 sp<SharedZip> zip = mZipFile[idx];
1646 // doesn't make sense to call before previously accessing.
1647 return zip->setResourceTableAsset(asset);
1648}
1649
1650/*
1651 * Generate the partial pathname for the specified archive. The caller
1652 * gets to prepend the asset root directory.
1653 *
1654 * Returns something like "common/en-US-noogle.jar".
1655 */
1656/*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath)
1657{
1658 return String8(zipPath);
1659}
1660
1661bool AssetManager::ZipSet::isUpToDate()
1662{
1663 const size_t N = mZipFile.size();
1664 for (size_t i=0; i<N; i++) {
1665 if (mZipFile[i] != NULL && !mZipFile[i]->isUpToDate()) {
1666 return false;
1667 }
1668 }
1669 return true;
1670}
1671
1672/*
1673 * Compute the zip file's index.
1674 *
1675 * "appName", "locale", and "vendor" should be set to NULL to indicate the
1676 * default directory.
1677 */
1678int AssetManager::ZipSet::getIndex(const String8& zip) const
1679{
1680 const size_t N = mZipPath.size();
1681 for (size_t i=0; i<N; i++) {
1682 if (mZipPath[i] == zip) {
1683 return i;
1684 }
1685 }
1686
1687 mZipPath.add(zip);
1688 mZipFile.add(NULL);
1689
1690 return mZipPath.size()-1;
1691}
1692