blob: 251d47b0ce8da696fa3e2b9df8305261243c8036 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//
18// Provide access to read-only assets.
19//
20
21#define LOG_TAG "asset"
Dianne Hackbornf7be4802013-04-12 14:52:58 -070022#define ATRACE_TAG ATRACE_TAG_RESOURCES
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023//#define LOG_NDEBUG 0
24
Mathias Agopianb13b9bd2012-02-17 18:27:36 -080025#include <androidfw/Asset.h>
26#include <androidfw/AssetDir.h>
27#include <androidfw/AssetManager.h>
Mathias Agopian1f5762e2013-05-06 20:20:34 -070028#include <androidfw/misc.h>
Mathias Agopianb13b9bd2012-02-17 18:27:36 -080029#include <androidfw/ResourceTypes.h>
Mathias Agopian1f5762e2013-05-06 20:20:34 -070030#include <androidfw/ZipFileRO.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031#include <utils/Atomic.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032#include <utils/Log.h>
Mathias Agopianb13b9bd2012-02-17 18:27:36 -080033#include <utils/String8.h>
34#include <utils/String8.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035#include <utils/threads.h>
Mathias Agopianb13b9bd2012-02-17 18:27:36 -080036#include <utils/Timers.h>
Dianne Hackbornc51d0502013-04-15 15:36:53 -070037#ifdef HAVE_ANDROID_OS
Dianne Hackbornf7be4802013-04-12 14:52:58 -070038#include <cutils/trace.h>
Dianne Hackbornc51d0502013-04-15 15:36:53 -070039#endif
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
Mathias Agopianb13b9bd2012-02-17 18:27:36 -080041#include <assert.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042#include <dirent.h>
43#include <errno.h>
Mårten Kongstad57f4b772011-03-17 14:13:41 +010044#include <fcntl.h>
Mathias Agopianb13b9bd2012-02-17 18:27:36 -080045#include <strings.h>
Mårten Kongstad57f4b772011-03-17 14:13:41 +010046#include <sys/stat.h>
47#include <unistd.h>
48
49#ifndef TEMP_FAILURE_RETRY
50/* Used to retry syscalls that can return EINTR. */
51#define TEMP_FAILURE_RETRY(exp) ({ \
52 typeof (exp) _rc; \
53 do { \
54 _rc = (exp); \
55 } while (_rc == -1 && errno == EINTR); \
56 _rc; })
57#endif
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058
Dianne Hackbornf7be4802013-04-12 14:52:58 -070059#ifdef HAVE_ANDROID_OS
60#define MY_TRACE_BEGIN(x) ATRACE_BEGIN(x)
61#define MY_TRACE_END() ATRACE_END()
62#else
63#define MY_TRACE_BEGIN(x)
64#define MY_TRACE_END()
65#endif
66
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067using namespace android;
68
69/*
70 * Names for default app, locale, and vendor. We might want to change
71 * these to be an actual locale, e.g. always use en-US as the default.
72 */
73static const char* kDefaultLocale = "default";
74static const char* kDefaultVendor = "default";
75static const char* kAssetsRoot = "assets";
76static const char* kAppZipName = NULL; //"classes.jar";
77static const char* kSystemAssets = "framework/framework-res.apk";
Mårten Kongstad57f4b772011-03-17 14:13:41 +010078static const char* kIdmapCacheDir = "resource-cache";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079
80static const char* kExcludeExtension = ".EXCLUDE";
81
82static Asset* const kExcludedAsset = (Asset*) 0xd000000d;
83
84static volatile int32_t gCount = 0;
85
Mårten Kongstad65a05fd2014-01-31 14:01:52 +010086const char* AssetManager::RESOURCES_FILENAME = "resources.arsc";
87
Mårten Kongstad57f4b772011-03-17 14:13:41 +010088namespace {
89 // Transform string /a/b/c.apk to /data/resource-cache/a@b@c.apk@idmap
90 String8 idmapPathForPackagePath(const String8& pkgPath)
91 {
92 const char* root = getenv("ANDROID_DATA");
93 LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
94 String8 path(root);
95 path.appendPath(kIdmapCacheDir);
96
97 char buf[256]; // 256 chars should be enough for anyone...
98 strncpy(buf, pkgPath.string(), 255);
99 buf[255] = '\0';
100 char* filename = buf;
101 while (*filename && *filename == '/') {
102 ++filename;
103 }
104 char* p = filename;
105 while (*p) {
106 if (*p == '/') {
107 *p = '@';
108 }
109 ++p;
110 }
111 path.appendPath(filename);
112 path.append("@idmap");
113
114 return path;
115 }
Mathias Agopian69a3ce12012-08-05 12:38:51 -0700116
117 /*
118 * Like strdup(), but uses C++ "new" operator instead of malloc.
119 */
120 static char* strdupNew(const char* str)
121 {
122 char* newStr;
123 int len;
124
125 if (str == NULL)
126 return NULL;
127
128 len = strlen(str);
129 newStr = new char[len+1];
130 memcpy(newStr, str, len+1);
131
132 return newStr;
133 }
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100134}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135
136/*
137 * ===========================================================================
138 * AssetManager
139 * ===========================================================================
140 */
141
142int32_t AssetManager::getGlobalCount()
143{
144 return gCount;
145}
146
147AssetManager::AssetManager(CacheMode cacheMode)
148 : mLocale(NULL), mVendor(NULL),
149 mResources(NULL), mConfig(new ResTable_config),
150 mCacheMode(cacheMode), mCacheValid(false)
151{
152 int count = android_atomic_inc(&gCount)+1;
Steve Block6215d3f2012-01-04 20:05:49 +0000153 //ALOGI("Creating AssetManager %p #%d\n", this, count);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 memset(mConfig, 0, sizeof(ResTable_config));
155}
156
157AssetManager::~AssetManager(void)
158{
159 int count = android_atomic_dec(&gCount);
Steve Block6215d3f2012-01-04 20:05:49 +0000160 //ALOGI("Destroying AssetManager in %p #%d\n", this, count);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161
162 delete mConfig;
163 delete mResources;
164
165 // don't have a String class yet, so make sure we clean up
166 delete[] mLocale;
167 delete[] mVendor;
168}
169
Narayan Kamath745d4ef2014-01-27 11:17:22 +0000170bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171{
172 AutoMutex _l(mLock);
173
174 asset_path ap;
175
176 String8 realPath(path);
177 if (kAppZipName) {
178 realPath.appendPath(kAppZipName);
179 }
180 ap.type = ::getFileType(realPath.string());
181 if (ap.type == kFileTypeRegular) {
182 ap.path = realPath;
183 } else {
184 ap.path = path;
185 ap.type = ::getFileType(path.string());
186 if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
Steve Block8564c8d2012-01-05 23:22:43 +0000187 ALOGW("Asset path %s is neither a directory nor file (type=%d).",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188 path.string(), (int)ap.type);
189 return false;
190 }
191 }
192
193 // Skip if we have it already.
194 for (size_t i=0; i<mAssetPaths.size(); i++) {
195 if (mAssetPaths[i].path == ap.path) {
196 if (cookie) {
Narayan Kamath745d4ef2014-01-27 11:17:22 +0000197 *cookie = static_cast<int32_t>(i+1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198 }
199 return true;
200 }
201 }
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100202
Steve Block71f2cf12011-10-20 11:56:00 +0100203 ALOGV("In %p Asset %s path: %s", this,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800204 ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
205
206 mAssetPaths.add(ap);
207
208 // new paths are always added at the end
209 if (cookie) {
Narayan Kamath745d4ef2014-01-27 11:17:22 +0000210 *cookie = static_cast<int32_t>(mAssetPaths.size());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 }
212
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100213 // add overlay packages for /system/framework; apps are handled by the
214 // (Java) package manager
215 if (strncmp(path.string(), "/system/framework/", 18) == 0) {
216 // When there is an environment variable for /vendor, this
217 // should be changed to something similar to how ANDROID_ROOT
218 // and ANDROID_DATA are used in this file.
219 String8 overlayPath("/vendor/overlay/framework/");
220 overlayPath.append(path.getPathLeaf());
221 if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {
222 asset_path oap;
223 oap.path = overlayPath;
224 oap.type = ::getFileType(overlayPath.string());
225 bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay
226 if (addOverlay) {
227 oap.idmap = idmapPathForPackagePath(overlayPath);
228
229 if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {
230 addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);
231 }
232 }
233 if (addOverlay) {
234 mAssetPaths.add(oap);
235 } else {
Steve Block8564c8d2012-01-05 23:22:43 +0000236 ALOGW("failed to add overlay package %s\n", overlayPath.string());
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100237 }
238 }
239 }
240
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 return true;
242}
243
Mårten Kongstad65a05fd2014-01-31 14:01:52 +0100244bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApkPath,
245 uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, uint32_t* outSize)
246{
247 AutoMutex _l(mLock);
248 const String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) };
249 ResTable tables[2];
250
251 for (int i = 0; i < 2; ++i) {
252 asset_path ap;
253 ap.type = kFileTypeRegular;
254 ap.path = paths[i];
255 Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
256 if (ass == NULL) {
257 ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
258 return false;
259 }
260 tables[i].add(ass, (void*)1, false);
261 }
262
263 return tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
264 targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
265}
266
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100267bool AssetManager::isIdmapStaleLocked(const String8& originalPath, const String8& overlayPath,
268 const String8& idmapPath)
269{
270 struct stat st;
271 if (TEMP_FAILURE_RETRY(stat(idmapPath.string(), &st)) == -1) {
272 if (errno == ENOENT) {
273 return true; // non-existing idmap is always stale
274 } else {
Steve Block8564c8d2012-01-05 23:22:43 +0000275 ALOGW("failed to stat file %s: %s\n", idmapPath.string(), strerror(errno));
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100276 return false;
277 }
278 }
279 if (st.st_size < ResTable::IDMAP_HEADER_SIZE_BYTES) {
Steve Block8564c8d2012-01-05 23:22:43 +0000280 ALOGW("file %s has unexpectedly small size=%zd\n", idmapPath.string(), (size_t)st.st_size);
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100281 return false;
282 }
283 int fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_RDONLY));
284 if (fd == -1) {
Steve Block8564c8d2012-01-05 23:22:43 +0000285 ALOGW("failed to open file %s: %s\n", idmapPath.string(), strerror(errno));
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100286 return false;
287 }
288 char buf[ResTable::IDMAP_HEADER_SIZE_BYTES];
289 ssize_t bytesLeft = ResTable::IDMAP_HEADER_SIZE_BYTES;
290 for (;;) {
291 ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf + ResTable::IDMAP_HEADER_SIZE_BYTES - bytesLeft,
292 bytesLeft));
293 if (r < 0) {
294 TEMP_FAILURE_RETRY(close(fd));
295 return false;
296 }
297 bytesLeft -= r;
298 if (bytesLeft == 0) {
299 break;
300 }
301 }
302 TEMP_FAILURE_RETRY(close(fd));
303
304 uint32_t cachedOriginalCrc, cachedOverlayCrc;
305 if (!ResTable::getIdmapInfo(buf, ResTable::IDMAP_HEADER_SIZE_BYTES,
306 &cachedOriginalCrc, &cachedOverlayCrc)) {
307 return false;
308 }
309
310 uint32_t actualOriginalCrc, actualOverlayCrc;
311 if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &actualOriginalCrc)) {
312 return false;
313 }
314 if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &actualOverlayCrc)) {
315 return false;
316 }
317 return cachedOriginalCrc != actualOriginalCrc || cachedOverlayCrc != actualOverlayCrc;
318}
319
320bool AssetManager::getZipEntryCrcLocked(const String8& zipPath, const char* entryFilename,
321 uint32_t* pCrc)
322{
323 asset_path ap;
324 ap.path = zipPath;
325 const ZipFileRO* zip = getZipFileLocked(ap);
326 if (zip == NULL) {
327 return false;
328 }
329 const ZipEntryRO entry = zip->findEntryByName(entryFilename);
330 if (entry == NULL) {
331 return false;
332 }
Narayan Kamathafd31e02013-12-03 13:16:03 +0000333
334 const bool gotInfo = zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, (long*)pCrc);
335 zip->releaseEntry(entry);
336
337 return gotInfo;
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100338}
339
340bool AssetManager::createIdmapFileLocked(const String8& originalPath, const String8& overlayPath,
341 const String8& idmapPath)
342{
Steve Block5baa3a62011-12-20 16:23:08 +0000343 ALOGD("%s: originalPath=%s overlayPath=%s idmapPath=%s\n",
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100344 __FUNCTION__, originalPath.string(), overlayPath.string(), idmapPath.string());
345 ResTable tables[2];
346 const String8* paths[2] = { &originalPath, &overlayPath };
347 uint32_t originalCrc, overlayCrc;
348 bool retval = false;
349 ssize_t offset = 0;
350 int fd = 0;
351 uint32_t* data = NULL;
352 size_t size;
353
354 for (int i = 0; i < 2; ++i) {
355 asset_path ap;
356 ap.type = kFileTypeRegular;
357 ap.path = *paths[i];
358 Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
359 if (ass == NULL) {
Steve Block8564c8d2012-01-05 23:22:43 +0000360 ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100361 goto error;
362 }
Narayan Kamath7c4887f2014-01-27 17:32:37 +0000363 tables[i].add(ass, 1, false /* copyData */, NULL /* idMap */);
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100364 }
365
366 if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &originalCrc)) {
Steve Block8564c8d2012-01-05 23:22:43 +0000367 ALOGW("failed to retrieve crc for resources.arsc in %s\n", originalPath.string());
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100368 goto error;
369 }
370 if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &overlayCrc)) {
Steve Block8564c8d2012-01-05 23:22:43 +0000371 ALOGW("failed to retrieve crc for resources.arsc in %s\n", overlayPath.string());
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100372 goto error;
373 }
374
375 if (tables[0].createIdmap(tables[1], originalCrc, overlayCrc,
376 (void**)&data, &size) != NO_ERROR) {
Steve Block8564c8d2012-01-05 23:22:43 +0000377 ALOGW("failed to generate idmap data for file %s\n", idmapPath.string());
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100378 goto error;
379 }
380
381 // This should be abstracted (eg replaced by a stand-alone
382 // application like dexopt, triggered by something equivalent to
383 // installd).
384 fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_WRONLY | O_CREAT | O_TRUNC, 0644));
385 if (fd == -1) {
Steve Block8564c8d2012-01-05 23:22:43 +0000386 ALOGW("failed to write idmap file %s (open: %s)\n", idmapPath.string(), strerror(errno));
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100387 goto error_free;
388 }
389 for (;;) {
390 ssize_t written = TEMP_FAILURE_RETRY(write(fd, data + offset, size));
391 if (written < 0) {
Steve Block8564c8d2012-01-05 23:22:43 +0000392 ALOGW("failed to write idmap file %s (write: %s)\n", idmapPath.string(),
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100393 strerror(errno));
394 goto error_close;
395 }
396 size -= (size_t)written;
397 offset += written;
398 if (size == 0) {
399 break;
400 }
401 }
402
403 retval = true;
404error_close:
405 TEMP_FAILURE_RETRY(close(fd));
406error_free:
407 free(data);
408error:
409 return retval;
410}
411
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412bool AssetManager::addDefaultAssets()
413{
414 const char* root = getenv("ANDROID_ROOT");
415 LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
416
417 String8 path(root);
418 path.appendPath(kSystemAssets);
419
420 return addAssetPath(path, NULL);
421}
422
Narayan Kamath745d4ef2014-01-27 11:17:22 +0000423int32_t AssetManager::nextAssetPath(const int32_t cookie) const
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424{
425 AutoMutex _l(mLock);
Narayan Kamath745d4ef2014-01-27 11:17:22 +0000426 const size_t next = static_cast<size_t>(cookie) + 1;
427 return next > mAssetPaths.size() ? -1 : next;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428}
429
Narayan Kamath745d4ef2014-01-27 11:17:22 +0000430String8 AssetManager::getAssetPath(const int32_t cookie) const
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431{
432 AutoMutex _l(mLock);
Narayan Kamath745d4ef2014-01-27 11:17:22 +0000433 const size_t which = static_cast<size_t>(cookie) - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434 if (which < mAssetPaths.size()) {
435 return mAssetPaths[which].path;
436 }
437 return String8();
438}
439
440/*
441 * Set the current locale. Use NULL to indicate no locale.
442 *
443 * Close and reopen Zip archives as appropriate, and reset cached
444 * information in the locale-specific sections of the tree.
445 */
446void AssetManager::setLocale(const char* locale)
447{
448 AutoMutex _l(mLock);
449 setLocaleLocked(locale);
450}
451
452void AssetManager::setLocaleLocked(const char* locale)
453{
454 if (mLocale != NULL) {
455 /* previously set, purge cached data */
456 purgeFileNameCacheLocked();
457 //mZipSet.purgeLocale();
458 delete[] mLocale;
459 }
460 mLocale = strdupNew(locale);
Elliott Hughesc367d482013-10-29 13:12:55 -0700461
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 updateResourceParamsLocked();
463}
464
465/*
466 * Set the current vendor. Use NULL to indicate no vendor.
467 *
468 * Close and reopen Zip archives as appropriate, and reset cached
469 * information in the vendor-specific sections of the tree.
470 */
471void AssetManager::setVendor(const char* vendor)
472{
473 AutoMutex _l(mLock);
474
475 if (mVendor != NULL) {
476 /* previously set, purge cached data */
477 purgeFileNameCacheLocked();
478 //mZipSet.purgeVendor();
479 delete[] mVendor;
480 }
481 mVendor = strdupNew(vendor);
482}
483
484void AssetManager::setConfiguration(const ResTable_config& config, const char* locale)
485{
486 AutoMutex _l(mLock);
487 *mConfig = config;
488 if (locale) {
489 setLocaleLocked(locale);
490 } else if (config.language[0] != 0) {
491 char spec[9];
492 spec[0] = config.language[0];
493 spec[1] = config.language[1];
494 if (config.country[0] != 0) {
495 spec[2] = '_';
496 spec[3] = config.country[0];
497 spec[4] = config.country[1];
498 spec[5] = 0;
499 } else {
500 spec[3] = 0;
501 }
502 setLocaleLocked(spec);
503 } else {
504 updateResourceParamsLocked();
505 }
506}
507
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -0700508void AssetManager::getConfiguration(ResTable_config* outConfig) const
509{
510 AutoMutex _l(mLock);
511 *outConfig = *mConfig;
512}
513
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514/*
515 * Open an asset.
516 *
517 * The data could be;
518 * - In a file on disk (assetBase + fileName).
519 * - In a compressed file on disk (assetBase + fileName.gz).
520 * - In a Zip archive, uncompressed or compressed.
521 *
522 * It can be in a number of different directories and Zip archives.
523 * The search order is:
524 * - [appname]
525 * - locale + vendor
526 * - "default" + vendor
527 * - locale + "default"
528 * - "default + "default"
529 * - "common"
530 * - (same as above)
531 *
532 * To find a particular file, we have to try up to eight paths with
533 * all three forms of data.
534 *
535 * We should probably reject requests for "illegal" filenames, e.g. those
536 * with illegal characters or "../" backward relative paths.
537 */
538Asset* AssetManager::open(const char* fileName, AccessMode mode)
539{
540 AutoMutex _l(mLock);
541
542 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
543
544
545 if (mCacheMode != CACHE_OFF && !mCacheValid)
546 loadFileNameCacheLocked();
547
548 String8 assetName(kAssetsRoot);
549 assetName.appendPath(fileName);
550
551 /*
552 * For each top-level asset path, search for the asset.
553 */
554
555 size_t i = mAssetPaths.size();
556 while (i > 0) {
557 i--;
Steve Block71f2cf12011-10-20 11:56:00 +0100558 ALOGV("Looking for asset '%s' in '%s'\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 assetName.string(), mAssetPaths.itemAt(i).path.string());
560 Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
561 if (pAsset != NULL) {
562 return pAsset != kExcludedAsset ? pAsset : NULL;
563 }
564 }
565
566 return NULL;
567}
568
569/*
570 * Open a non-asset file as if it were an asset.
571 *
572 * The "fileName" is the partial path starting from the application
573 * name.
574 */
575Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode)
576{
577 AutoMutex _l(mLock);
578
579 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
580
581
582 if (mCacheMode != CACHE_OFF && !mCacheValid)
583 loadFileNameCacheLocked();
584
585 /*
586 * For each top-level asset path, search for the asset.
587 */
588
589 size_t i = mAssetPaths.size();
590 while (i > 0) {
591 i--;
Steve Block71f2cf12011-10-20 11:56:00 +0100592 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 Asset* pAsset = openNonAssetInPathLocked(
594 fileName, mode, mAssetPaths.itemAt(i));
595 if (pAsset != NULL) {
596 return pAsset != kExcludedAsset ? pAsset : NULL;
597 }
598 }
599
600 return NULL;
601}
602
Narayan Kamath745d4ef2014-01-27 11:17:22 +0000603Asset* AssetManager::openNonAsset(const int32_t cookie, const char* fileName, AccessMode mode)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604{
Narayan Kamath745d4ef2014-01-27 11:17:22 +0000605 const size_t which = static_cast<size_t>(cookie) - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606
607 AutoMutex _l(mLock);
608
609 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
610
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 if (mCacheMode != CACHE_OFF && !mCacheValid)
612 loadFileNameCacheLocked();
613
614 if (which < mAssetPaths.size()) {
Steve Block71f2cf12011-10-20 11:56:00 +0100615 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 mAssetPaths.itemAt(which).path.string());
617 Asset* pAsset = openNonAssetInPathLocked(
618 fileName, mode, mAssetPaths.itemAt(which));
619 if (pAsset != NULL) {
620 return pAsset != kExcludedAsset ? pAsset : NULL;
621 }
622 }
623
624 return NULL;
625}
626
627/*
628 * Get the type of a file in the asset namespace.
629 *
630 * This currently only works for regular files. All others (including
631 * directories) will return kFileTypeNonexistent.
632 */
633FileType AssetManager::getFileType(const char* fileName)
634{
635 Asset* pAsset = NULL;
636
637 /*
638 * Open the asset. This is less efficient than simply finding the
639 * file, but it's not too bad (we don't uncompress or mmap data until
640 * the first read() call).
641 */
642 pAsset = open(fileName, Asset::ACCESS_STREAMING);
643 delete pAsset;
644
645 if (pAsset == NULL)
646 return kFileTypeNonexistent;
647 else
648 return kFileTypeRegular;
649}
650
651const ResTable* AssetManager::getResTable(bool required) const
652{
653 ResTable* rt = mResources;
654 if (rt) {
655 return rt;
656 }
657
658 // Iterate through all asset packages, collecting resources from each.
659
660 AutoMutex _l(mLock);
661
662 if (mResources != NULL) {
663 return mResources;
664 }
665
666 if (required) {
667 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
668 }
669
670 if (mCacheMode != CACHE_OFF && !mCacheValid)
671 const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
672
673 const size_t N = mAssetPaths.size();
674 for (size_t i=0; i<N; i++) {
675 Asset* ass = NULL;
Dianne Hackborn78c40512009-07-06 11:07:40 -0700676 ResTable* sharedRes = NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800677 bool shared = true;
678 const asset_path& ap = mAssetPaths.itemAt(i);
Dianne Hackbornf7be4802013-04-12 14:52:58 -0700679 MY_TRACE_BEGIN(ap.path.string());
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100680 Asset* idmap = openIdmapLocked(ap);
Steve Block71f2cf12011-10-20 11:56:00 +0100681 ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682 if (ap.type != kFileTypeDirectory) {
Dianne Hackborn78c40512009-07-06 11:07:40 -0700683 if (i == 0) {
684 // The first item is typically the framework resources,
685 // which we want to avoid parsing every time.
686 sharedRes = const_cast<AssetManager*>(this)->
687 mZipSet.getZipResourceTable(ap.path);
688 }
689 if (sharedRes == NULL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690 ass = const_cast<AssetManager*>(this)->
Dianne Hackborn78c40512009-07-06 11:07:40 -0700691 mZipSet.getZipResourceTableAsset(ap.path);
692 if (ass == NULL) {
Steve Block71f2cf12011-10-20 11:56:00 +0100693 ALOGV("loading resource table %s\n", ap.path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 ass = const_cast<AssetManager*>(this)->
Dianne Hackborn78c40512009-07-06 11:07:40 -0700695 openNonAssetInPathLocked("resources.arsc",
696 Asset::ACCESS_BUFFER,
697 ap);
698 if (ass != NULL && ass != kExcludedAsset) {
699 ass = const_cast<AssetManager*>(this)->
700 mZipSet.setZipResourceTableAsset(ap.path, ass);
701 }
702 }
Elliott Hughesc367d482013-10-29 13:12:55 -0700703
Dianne Hackborn78c40512009-07-06 11:07:40 -0700704 if (i == 0 && ass != NULL) {
705 // If this is the first resource table in the asset
706 // manager, then we are going to cache it so that we
707 // can quickly copy it out for others.
Steve Block71f2cf12011-10-20 11:56:00 +0100708 ALOGV("Creating shared resources for %s", ap.path.string());
Dianne Hackborn78c40512009-07-06 11:07:40 -0700709 sharedRes = new ResTable();
Narayan Kamath7c4887f2014-01-27 17:32:37 +0000710 sharedRes->add(ass, i + 1, false, idmap);
Dianne Hackborn78c40512009-07-06 11:07:40 -0700711 sharedRes = const_cast<AssetManager*>(this)->
712 mZipSet.setZipResourceTable(ap.path, sharedRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 }
714 }
715 } else {
Steve Block71f2cf12011-10-20 11:56:00 +0100716 ALOGV("loading resource table %s\n", ap.path.string());
Elliott Hughesc367d482013-10-29 13:12:55 -0700717 ass = const_cast<AssetManager*>(this)->
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800718 openNonAssetInPathLocked("resources.arsc",
719 Asset::ACCESS_BUFFER,
720 ap);
721 shared = false;
722 }
Dianne Hackborn78c40512009-07-06 11:07:40 -0700723 if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800724 if (rt == NULL) {
725 mResources = rt = new ResTable();
726 updateResourceParamsLocked();
727 }
Steve Block71f2cf12011-10-20 11:56:00 +0100728 ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
Dianne Hackborn78c40512009-07-06 11:07:40 -0700729 if (sharedRes != NULL) {
Steve Block71f2cf12011-10-20 11:56:00 +0100730 ALOGV("Copying existing resources for %s", ap.path.string());
Dianne Hackborn78c40512009-07-06 11:07:40 -0700731 rt->add(sharedRes);
732 } else {
Steve Block71f2cf12011-10-20 11:56:00 +0100733 ALOGV("Parsing resources for %s", ap.path.string());
Narayan Kamath7c4887f2014-01-27 17:32:37 +0000734 rt->add(ass, i + 1, !shared, idmap);
Dianne Hackborn78c40512009-07-06 11:07:40 -0700735 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800736
737 if (!shared) {
738 delete ass;
739 }
740 }
Mårten Kongstad2b91c672011-05-05 10:40:42 +0200741 if (idmap != NULL) {
742 delete idmap;
743 }
Dianne Hackbornf7be4802013-04-12 14:52:58 -0700744 MY_TRACE_END();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800745 }
746
Steve Block8564c8d2012-01-05 23:22:43 +0000747 if (required && !rt) ALOGW("Unable to find resources file resources.arsc");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800748 if (!rt) {
749 mResources = rt = new ResTable();
750 }
751 return rt;
752}
753
754void AssetManager::updateResourceParamsLocked() const
755{
756 ResTable* res = mResources;
757 if (!res) {
758 return;
759 }
760
761 size_t llen = mLocale ? strlen(mLocale) : 0;
762 mConfig->language[0] = 0;
763 mConfig->language[1] = 0;
764 mConfig->country[0] = 0;
765 mConfig->country[1] = 0;
766 if (llen >= 2) {
767 mConfig->language[0] = mLocale[0];
768 mConfig->language[1] = mLocale[1];
769 }
770 if (llen >= 5) {
771 mConfig->country[0] = mLocale[3];
772 mConfig->country[1] = mLocale[4];
773 }
774 mConfig->size = sizeof(*mConfig);
775
776 res->setParameters(mConfig);
777}
778
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100779Asset* AssetManager::openIdmapLocked(const struct asset_path& ap) const
780{
781 Asset* ass = NULL;
782 if (ap.idmap.size() != 0) {
783 ass = const_cast<AssetManager*>(this)->
784 openAssetFromFileLocked(ap.idmap, Asset::ACCESS_BUFFER);
785 if (ass) {
Steve Block71f2cf12011-10-20 11:56:00 +0100786 ALOGV("loading idmap %s\n", ap.idmap.string());
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100787 } else {
Steve Block8564c8d2012-01-05 23:22:43 +0000788 ALOGW("failed to load idmap %s\n", ap.idmap.string());
Mårten Kongstad57f4b772011-03-17 14:13:41 +0100789 }
790 }
791 return ass;
792}
793
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794const ResTable& AssetManager::getResources(bool required) const
795{
796 const ResTable* rt = getResTable(required);
797 return *rt;
798}
799
800bool AssetManager::isUpToDate()
801{
802 AutoMutex _l(mLock);
803 return mZipSet.isUpToDate();
804}
805
806void AssetManager::getLocales(Vector<String8>* locales) const
807{
808 ResTable* res = mResources;
809 if (res != NULL) {
810 res->getLocales(locales);
811 }
812}
813
814/*
815 * Open a non-asset file as if it were an asset, searching for it in the
816 * specified app.
817 *
818 * Pass in a NULL values for "appName" if the common app directory should
819 * be used.
820 */
821Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
822 const asset_path& ap)
823{
824 Asset* pAsset = NULL;
825
826 /* look at the filesystem on disk */
827 if (ap.type == kFileTypeDirectory) {
828 String8 path(ap.path);
829 path.appendPath(fileName);
830
831 pAsset = openAssetFromFileLocked(path, mode);
832
833 if (pAsset == NULL) {
834 /* try again, this time with ".gz" */
835 path.append(".gz");
836 pAsset = openAssetFromFileLocked(path, mode);
837 }
838
839 if (pAsset != NULL) {
840 //printf("FOUND NA '%s' on disk\n", fileName);
841 pAsset->setAssetSource(path);
842 }
843
844 /* look inside the zip file */
845 } else {
846 String8 path(fileName);
847
848 /* check the appropriate Zip file */
Narayan Kamathafd31e02013-12-03 13:16:03 +0000849 ZipFileRO* pZip = getZipFileLocked(ap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 if (pZip != NULL) {
851 //printf("GOT zip, checking NA '%s'\n", (const char*) path);
Narayan Kamathafd31e02013-12-03 13:16:03 +0000852 ZipEntryRO entry = pZip->findEntryByName(path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 if (entry != NULL) {
854 //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
855 pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
Narayan Kamathafd31e02013-12-03 13:16:03 +0000856 pZip->releaseEntry(entry);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 }
858 }
859
860 if (pAsset != NULL) {
861 /* create a "source" name, for debug/display */
862 pAsset->setAssetSource(
863 createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
864 String8(fileName)));
865 }
866 }
867
868 return pAsset;
869}
870
871/*
872 * Open an asset, searching for it in the directory hierarchy for the
873 * specified app.
874 *
875 * Pass in a NULL values for "appName" if the common app directory should
876 * be used.
877 */
878Asset* AssetManager::openInPathLocked(const char* fileName, AccessMode mode,
879 const asset_path& ap)
880{
881 Asset* pAsset = NULL;
882
883 /*
884 * Try various combinations of locale and vendor.
885 */
886 if (mLocale != NULL && mVendor != NULL)
887 pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, mVendor);
888 if (pAsset == NULL && mVendor != NULL)
889 pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, mVendor);
890 if (pAsset == NULL && mLocale != NULL)
891 pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, NULL);
892 if (pAsset == NULL)
893 pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, NULL);
894
895 return pAsset;
896}
897
898/*
899 * Open an asset, searching for it in the directory hierarchy for the
900 * specified locale and vendor.
901 *
902 * We also search in "app.jar".
903 *
904 * Pass in NULL values for "appName", "locale", and "vendor" if the
905 * defaults should be used.
906 */
907Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode mode,
908 const asset_path& ap, const char* locale, const char* vendor)
909{
910 Asset* pAsset = NULL;
911
912 if (ap.type == kFileTypeDirectory) {
913 if (mCacheMode == CACHE_OFF) {
914 /* look at the filesystem on disk */
915 String8 path(createPathNameLocked(ap, locale, vendor));
916 path.appendPath(fileName);
Elliott Hughesc367d482013-10-29 13:12:55 -0700917
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800918 String8 excludeName(path);
919 excludeName.append(kExcludeExtension);
920 if (::getFileType(excludeName.string()) != kFileTypeNonexistent) {
921 /* say no more */
922 //printf("+++ excluding '%s'\n", (const char*) excludeName);
923 return kExcludedAsset;
924 }
Elliott Hughesc367d482013-10-29 13:12:55 -0700925
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 pAsset = openAssetFromFileLocked(path, mode);
Elliott Hughesc367d482013-10-29 13:12:55 -0700927
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 if (pAsset == NULL) {
929 /* try again, this time with ".gz" */
930 path.append(".gz");
931 pAsset = openAssetFromFileLocked(path, mode);
932 }
Elliott Hughesc367d482013-10-29 13:12:55 -0700933
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 if (pAsset != NULL)
935 pAsset->setAssetSource(path);
936 } else {
937 /* find in cache */
938 String8 path(createPathNameLocked(ap, locale, vendor));
939 path.appendPath(fileName);
Elliott Hughesc367d482013-10-29 13:12:55 -0700940
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 AssetDir::FileInfo tmpInfo;
942 bool found = false;
Elliott Hughesc367d482013-10-29 13:12:55 -0700943
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 String8 excludeName(path);
945 excludeName.append(kExcludeExtension);
Elliott Hughesc367d482013-10-29 13:12:55 -0700946
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) {
948 /* go no farther */
949 //printf("+++ Excluding '%s'\n", (const char*) excludeName);
950 return kExcludedAsset;
951 }
952
953 /*
954 * File compression extensions (".gz") don't get stored in the
955 * name cache, so we have to try both here.
956 */
957 if (mCache.indexOf(path) != NAME_NOT_FOUND) {
958 found = true;
959 pAsset = openAssetFromFileLocked(path, mode);
960 if (pAsset == NULL) {
961 /* try again, this time with ".gz" */
962 path.append(".gz");
963 pAsset = openAssetFromFileLocked(path, mode);
964 }
965 }
966
967 if (pAsset != NULL)
968 pAsset->setAssetSource(path);
969
970 /*
971 * Don't continue the search into the Zip files. Our cached info
972 * said it was a file on disk; to be consistent with openDir()
973 * we want to return the loose asset. If the cached file gets
974 * removed, we fail.
975 *
976 * The alternative is to update our cache when files get deleted,
977 * or make some sort of "best effort" promise, but for now I'm
978 * taking the hard line.
979 */
980 if (found) {
981 if (pAsset == NULL)
Steve Block5baa3a62011-12-20 16:23:08 +0000982 ALOGD("Expected file not found: '%s'\n", path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800983 return pAsset;
984 }
985 }
986 }
987
988 /*
989 * Either it wasn't found on disk or on the cached view of the disk.
990 * Dig through the currently-opened set of Zip files. If caching
991 * is disabled, the Zip file may get reopened.
992 */
993 if (pAsset == NULL && ap.type == kFileTypeRegular) {
994 String8 path;
995
996 path.appendPath((locale != NULL) ? locale : kDefaultLocale);
997 path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
998 path.appendPath(fileName);
999
1000 /* check the appropriate Zip file */
Narayan Kamathafd31e02013-12-03 13:16:03 +00001001 ZipFileRO* pZip = getZipFileLocked(ap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001002 if (pZip != NULL) {
1003 //printf("GOT zip, checking '%s'\n", (const char*) path);
Narayan Kamathafd31e02013-12-03 13:16:03 +00001004 ZipEntryRO entry = pZip->findEntryByName(path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001005 if (entry != NULL) {
1006 //printf("FOUND in Zip file for %s/%s-%s\n",
1007 // appName, locale, vendor);
1008 pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
Narayan Kamathafd31e02013-12-03 13:16:03 +00001009 pZip->releaseEntry(entry);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001010 }
1011 }
1012
1013 if (pAsset != NULL) {
1014 /* create a "source" name, for debug/display */
1015 pAsset->setAssetSource(createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()),
1016 String8(""), String8(fileName)));
1017 }
1018 }
1019
1020 return pAsset;
1021}
1022
1023/*
1024 * Create a "source name" for a file from a Zip archive.
1025 */
1026String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName,
1027 const String8& dirName, const String8& fileName)
1028{
1029 String8 sourceName("zip:");
1030 sourceName.append(zipFileName);
1031 sourceName.append(":");
1032 if (dirName.length() > 0) {
1033 sourceName.appendPath(dirName);
1034 }
1035 sourceName.appendPath(fileName);
1036 return sourceName;
1037}
1038
1039/*
1040 * Create a path to a loose asset (asset-base/app/locale/vendor).
1041 */
1042String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* locale,
1043 const char* vendor)
1044{
1045 String8 path(ap.path);
1046 path.appendPath((locale != NULL) ? locale : kDefaultLocale);
1047 path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
1048 return path;
1049}
1050
1051/*
1052 * Create a path to a loose asset (asset-base/app/rootDir).
1053 */
1054String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir)
1055{
1056 String8 path(ap.path);
1057 if (rootDir != NULL) path.appendPath(rootDir);
1058 return path;
1059}
1060
1061/*
1062 * Return a pointer to one of our open Zip archives. Returns NULL if no
1063 * matching Zip file exists.
1064 *
1065 * Right now we have 2 possible Zip files (1 each in app/"common").
1066 *
1067 * If caching is set to CACHE_OFF, to get the expected behavior we
1068 * need to reopen the Zip file on every request. That would be silly
1069 * and expensive, so instead we just check the file modification date.
1070 *
1071 * Pass in NULL values for "appName", "locale", and "vendor" if the
1072 * generics should be used.
1073 */
1074ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
1075{
Steve Block71f2cf12011-10-20 11:56:00 +01001076 ALOGV("getZipFileLocked() in %p\n", this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077
1078 return mZipSet.getZip(ap.path);
1079}
1080
1081/*
1082 * Try to open an asset from a file on disk.
1083 *
1084 * If the file is compressed with gzip, we seek to the start of the
1085 * deflated data and pass that in (just like we would for a Zip archive).
1086 *
1087 * For uncompressed data, we may already have an mmap()ed version sitting
1088 * around. If so, we want to hand that to the Asset instead.
1089 *
1090 * This returns NULL if the file doesn't exist, couldn't be opened, or
1091 * claims to be a ".gz" but isn't.
1092 */
1093Asset* AssetManager::openAssetFromFileLocked(const String8& pathName,
1094 AccessMode mode)
1095{
1096 Asset* pAsset = NULL;
1097
1098 if (strcasecmp(pathName.getPathExtension().string(), ".gz") == 0) {
1099 //printf("TRYING '%s'\n", (const char*) pathName);
1100 pAsset = Asset::createFromCompressedFile(pathName.string(), mode);
1101 } else {
1102 //printf("TRYING '%s'\n", (const char*) pathName);
1103 pAsset = Asset::createFromFile(pathName.string(), mode);
1104 }
1105
1106 return pAsset;
1107}
1108
1109/*
1110 * Given an entry in a Zip archive, create a new Asset object.
1111 *
1112 * If the entry is uncompressed, we may want to create or share a
1113 * slice of shared memory.
1114 */
1115Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile,
1116 const ZipEntryRO entry, AccessMode mode, const String8& entryName)
1117{
1118 Asset* pAsset = NULL;
1119
1120 // TODO: look for previously-created shared memory slice?
1121 int method;
Kenny Root68246dc2010-04-22 18:28:29 -07001122 size_t uncompressedLen;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001123
1124 //printf("USING Zip '%s'\n", pEntry->getFileName());
1125
1126 //pZipFile->getEntryInfo(entry, &method, &uncompressedLen, &compressedLen,
1127 // &offset);
1128 if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL,
1129 NULL, NULL))
1130 {
Steve Block8564c8d2012-01-05 23:22:43 +00001131 ALOGW("getEntryInfo failed\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132 return NULL;
1133 }
1134
1135 FileMap* dataMap = pZipFile->createEntryFileMap(entry);
1136 if (dataMap == NULL) {
Steve Block8564c8d2012-01-05 23:22:43 +00001137 ALOGW("create map from entry failed\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138 return NULL;
1139 }
1140
1141 if (method == ZipFileRO::kCompressStored) {
1142 pAsset = Asset::createFromUncompressedMap(dataMap, mode);
Steve Block71f2cf12011-10-20 11:56:00 +01001143 ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001144 dataMap->getFileName(), mode, pAsset);
1145 } else {
1146 pAsset = Asset::createFromCompressedMap(dataMap, method,
1147 uncompressedLen, mode);
Steve Block71f2cf12011-10-20 11:56:00 +01001148 ALOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001149 dataMap->getFileName(), mode, pAsset);
1150 }
1151 if (pAsset == NULL) {
1152 /* unexpected */
Steve Block8564c8d2012-01-05 23:22:43 +00001153 ALOGW("create from segment failed\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 }
1155
1156 return pAsset;
1157}
1158
1159
1160
1161/*
1162 * Open a directory in the asset namespace.
1163 *
1164 * An "asset directory" is simply the combination of all files in all
1165 * locations, with ".gz" stripped for loose files. With app, locale, and
1166 * vendor defined, we have 8 directories and 2 Zip archives to scan.
1167 *
1168 * Pass in "" for the root dir.
1169 */
1170AssetDir* AssetManager::openDir(const char* dirName)
1171{
1172 AutoMutex _l(mLock);
1173
1174 AssetDir* pDir = NULL;
1175 SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL;
1176
1177 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
1178 assert(dirName != NULL);
1179
1180 //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
1181
1182 if (mCacheMode != CACHE_OFF && !mCacheValid)
1183 loadFileNameCacheLocked();
1184
1185 pDir = new AssetDir;
1186
1187 /*
1188 * Scan the various directories, merging what we find into a single
1189 * vector. We want to scan them in reverse priority order so that
1190 * the ".EXCLUDE" processing works correctly. Also, if we decide we
1191 * want to remember where the file is coming from, we'll get the right
1192 * version.
1193 *
1194 * We start with Zip archives, then do loose files.
1195 */
1196 pMergedInfo = new SortedVector<AssetDir::FileInfo>;
1197
1198 size_t i = mAssetPaths.size();
1199 while (i > 0) {
1200 i--;
1201 const asset_path& ap = mAssetPaths.itemAt(i);
1202 if (ap.type == kFileTypeRegular) {
Steve Block71f2cf12011-10-20 11:56:00 +01001203 ALOGV("Adding directory %s from zip %s", dirName, ap.path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName);
1205 } else {
Steve Block71f2cf12011-10-20 11:56:00 +01001206 ALOGV("Adding directory %s from dir %s", dirName, ap.path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001207 scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName);
1208 }
1209 }
1210
1211#if 0
1212 printf("FILE LIST:\n");
1213 for (i = 0; i < (size_t) pMergedInfo->size(); i++) {
1214 printf(" %d: (%d) '%s'\n", i,
1215 pMergedInfo->itemAt(i).getFileType(),
1216 (const char*) pMergedInfo->itemAt(i).getFileName());
1217 }
1218#endif
1219
1220 pDir->setFileList(pMergedInfo);
1221 return pDir;
1222}
1223
1224/*
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001225 * Open a directory in the non-asset namespace.
1226 *
1227 * An "asset directory" is simply the combination of all files in all
1228 * locations, with ".gz" stripped for loose files. With app, locale, and
1229 * vendor defined, we have 8 directories and 2 Zip archives to scan.
1230 *
1231 * Pass in "" for the root dir.
1232 */
Narayan Kamath745d4ef2014-01-27 11:17:22 +00001233AssetDir* AssetManager::openNonAssetDir(const int32_t cookie, const char* dirName)
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001234{
1235 AutoMutex _l(mLock);
1236
1237 AssetDir* pDir = NULL;
1238 SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL;
1239
1240 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
1241 assert(dirName != NULL);
1242
1243 //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
1244
1245 if (mCacheMode != CACHE_OFF && !mCacheValid)
1246 loadFileNameCacheLocked();
1247
1248 pDir = new AssetDir;
1249
1250 pMergedInfo = new SortedVector<AssetDir::FileInfo>;
1251
Narayan Kamath745d4ef2014-01-27 11:17:22 +00001252 const size_t which = static_cast<size_t>(cookie) - 1;
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001253
1254 if (which < mAssetPaths.size()) {
1255 const asset_path& ap = mAssetPaths.itemAt(which);
1256 if (ap.type == kFileTypeRegular) {
Steve Block71f2cf12011-10-20 11:56:00 +01001257 ALOGV("Adding directory %s from zip %s", dirName, ap.path.string());
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001258 scanAndMergeZipLocked(pMergedInfo, ap, NULL, dirName);
1259 } else {
Steve Block71f2cf12011-10-20 11:56:00 +01001260 ALOGV("Adding directory %s from dir %s", dirName, ap.path.string());
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001261 scanAndMergeDirLocked(pMergedInfo, ap, NULL, dirName);
1262 }
1263 }
1264
1265#if 0
1266 printf("FILE LIST:\n");
1267 for (i = 0; i < (size_t) pMergedInfo->size(); i++) {
1268 printf(" %d: (%d) '%s'\n", i,
1269 pMergedInfo->itemAt(i).getFileType(),
1270 (const char*) pMergedInfo->itemAt(i).getFileName());
1271 }
1272#endif
1273
1274 pDir->setFileList(pMergedInfo);
1275 return pDir;
1276}
1277
1278/*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001279 * Scan the contents of the specified directory and merge them into the
1280 * "pMergedInfo" vector, removing previous entries if we find "exclude"
1281 * directives.
1282 *
1283 * Returns "false" if we found nothing to contribute.
1284 */
1285bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1286 const asset_path& ap, const char* rootDir, const char* dirName)
1287{
1288 SortedVector<AssetDir::FileInfo>* pContents;
1289 String8 path;
1290
1291 assert(pMergedInfo != NULL);
1292
1293 //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName);
1294
1295 if (mCacheValid) {
1296 int i, start, count;
1297
1298 pContents = new SortedVector<AssetDir::FileInfo>;
1299
1300 /*
1301 * Get the basic partial path and find it in the cache. That's
1302 * the start point for the search.
1303 */
1304 path = createPathNameLocked(ap, rootDir);
1305 if (dirName[0] != '\0')
1306 path.appendPath(dirName);
1307
1308 start = mCache.indexOf(path);
1309 if (start == NAME_NOT_FOUND) {
1310 //printf("+++ not found in cache: dir '%s'\n", (const char*) path);
1311 delete pContents;
1312 return false;
1313 }
1314
1315 /*
1316 * The match string looks like "common/default/default/foo/bar/".
1317 * The '/' on the end ensures that we don't match on the directory
1318 * itself or on ".../foo/barfy/".
1319 */
1320 path.append("/");
1321
1322 count = mCache.size();
1323
1324 /*
1325 * Pick out the stuff in the current dir by examining the pathname.
1326 * It needs to match the partial pathname prefix, and not have a '/'
1327 * (fssep) anywhere after the prefix.
1328 */
1329 for (i = start+1; i < count; i++) {
1330 if (mCache[i].getFileName().length() > path.length() &&
1331 strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0)
1332 {
1333 const char* name = mCache[i].getFileName().string();
1334 // XXX THIS IS BROKEN! Looks like we need to store the full
1335 // path prefix separately from the file path.
1336 if (strchr(name + path.length(), '/') == NULL) {
1337 /* grab it, reducing path to just the filename component */
1338 AssetDir::FileInfo tmp = mCache[i];
1339 tmp.setFileName(tmp.getFileName().getPathLeaf());
1340 pContents->add(tmp);
1341 }
1342 } else {
1343 /* no longer in the dir or its subdirs */
1344 break;
1345 }
1346
1347 }
1348 } else {
1349 path = createPathNameLocked(ap, rootDir);
1350 if (dirName[0] != '\0')
1351 path.appendPath(dirName);
1352 pContents = scanDirLocked(path);
1353 if (pContents == NULL)
1354 return false;
1355 }
1356
1357 // if we wanted to do an incremental cache fill, we would do it here
1358
1359 /*
1360 * Process "exclude" directives. If we find a filename that ends with
1361 * ".EXCLUDE", we look for a matching entry in the "merged" set, and
1362 * remove it if we find it. We also delete the "exclude" entry.
1363 */
1364 int i, count, exclExtLen;
1365
1366 count = pContents->size();
1367 exclExtLen = strlen(kExcludeExtension);
1368 for (i = 0; i < count; i++) {
1369 const char* name;
1370 int nameLen;
1371
1372 name = pContents->itemAt(i).getFileName().string();
1373 nameLen = strlen(name);
1374 if (nameLen > exclExtLen &&
1375 strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0)
1376 {
1377 String8 match(name, nameLen - exclExtLen);
1378 int matchIdx;
1379
1380 matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match);
1381 if (matchIdx > 0) {
Steve Block71f2cf12011-10-20 11:56:00 +01001382 ALOGV("Excluding '%s' [%s]\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001383 pMergedInfo->itemAt(matchIdx).getFileName().string(),
1384 pMergedInfo->itemAt(matchIdx).getSourceName().string());
1385 pMergedInfo->removeAt(matchIdx);
1386 } else {
1387 //printf("+++ no match on '%s'\n", (const char*) match);
1388 }
1389
Steve Block5baa3a62011-12-20 16:23:08 +00001390 ALOGD("HEY: size=%d removing %d\n", (int)pContents->size(), i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001391 pContents->removeAt(i);
1392 i--; // adjust "for" loop
1393 count--; // and loop limit
1394 }
1395 }
1396
1397 mergeInfoLocked(pMergedInfo, pContents);
1398
1399 delete pContents;
1400
1401 return true;
1402}
1403
1404/*
1405 * Scan the contents of the specified directory, and stuff what we find
1406 * into a newly-allocated vector.
1407 *
1408 * Files ending in ".gz" will have their extensions removed.
1409 *
1410 * We should probably think about skipping files with "illegal" names,
1411 * e.g. illegal characters (/\:) or excessive length.
1412 *
1413 * Returns NULL if the specified directory doesn't exist.
1414 */
1415SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& path)
1416{
1417 SortedVector<AssetDir::FileInfo>* pContents = NULL;
1418 DIR* dir;
1419 struct dirent* entry;
1420 FileType fileType;
1421
Steve Block71f2cf12011-10-20 11:56:00 +01001422 ALOGV("Scanning dir '%s'\n", path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001423
1424 dir = opendir(path.string());
1425 if (dir == NULL)
1426 return NULL;
1427
1428 pContents = new SortedVector<AssetDir::FileInfo>;
1429
1430 while (1) {
1431 entry = readdir(dir);
1432 if (entry == NULL)
1433 break;
1434
1435 if (strcmp(entry->d_name, ".") == 0 ||
1436 strcmp(entry->d_name, "..") == 0)
1437 continue;
1438
1439#ifdef _DIRENT_HAVE_D_TYPE
1440 if (entry->d_type == DT_REG)
1441 fileType = kFileTypeRegular;
1442 else if (entry->d_type == DT_DIR)
1443 fileType = kFileTypeDirectory;
1444 else
1445 fileType = kFileTypeUnknown;
1446#else
1447 // stat the file
1448 fileType = ::getFileType(path.appendPathCopy(entry->d_name).string());
1449#endif
1450
1451 if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory)
1452 continue;
1453
1454 AssetDir::FileInfo info;
1455 info.set(String8(entry->d_name), fileType);
1456 if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0)
1457 info.setFileName(info.getFileName().getBasePath());
1458 info.setSourceName(path.appendPathCopy(info.getFileName()));
1459 pContents->add(info);
1460 }
1461
1462 closedir(dir);
1463 return pContents;
1464}
1465
1466/*
1467 * Scan the contents out of the specified Zip archive, and merge what we
1468 * find into "pMergedInfo". If the Zip archive in question doesn't exist,
1469 * we return immediately.
1470 *
1471 * Returns "false" if we found nothing to contribute.
1472 */
1473bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1474 const asset_path& ap, const char* rootDir, const char* baseDirName)
1475{
1476 ZipFileRO* pZip;
1477 Vector<String8> dirs;
1478 AssetDir::FileInfo info;
1479 SortedVector<AssetDir::FileInfo> contents;
1480 String8 sourceName, zipName, dirName;
1481
1482 pZip = mZipSet.getZip(ap.path);
1483 if (pZip == NULL) {
Steve Block8564c8d2012-01-05 23:22:43 +00001484 ALOGW("Failure opening zip %s\n", ap.path.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001485 return false;
1486 }
1487
1488 zipName = ZipSet::getPathName(ap.path.string());
1489
1490 /* convert "sounds" to "rootDir/sounds" */
1491 if (rootDir != NULL) dirName = rootDir;
1492 dirName.appendPath(baseDirName);
1493
1494 /*
1495 * Scan through the list of files, looking for a match. The files in
1496 * the Zip table of contents are not in sorted order, so we have to
1497 * process the entire list. We're looking for a string that begins
1498 * with the characters in "dirName", is followed by a '/', and has no
1499 * subsequent '/' in the stuff that follows.
1500 *
1501 * What makes this especially fun is that directories are not stored
1502 * explicitly in Zip archives, so we have to infer them from context.
1503 * When we see "sounds/foo.wav" we have to leave a note to ourselves
1504 * to insert a directory called "sounds" into the list. We store
1505 * these in temporary vector so that we only return each one once.
1506 *
1507 * Name comparisons are case-sensitive to match UNIX filesystem
1508 * semantics.
1509 */
1510 int dirNameLen = dirName.length();
Narayan Kamathafd31e02013-12-03 13:16:03 +00001511 void *iterationCookie;
1512 if (!pZip->startIteration(&iterationCookie)) {
1513 ALOGW("ZipFileRO::startIteration returned false");
1514 return false;
1515 }
1516
1517 ZipEntryRO entry;
1518 while ((entry = pZip->nextEntry(iterationCookie)) != NULL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001519 char nameBuf[256];
1520
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001521 if (pZip->getEntryFileName(entry, nameBuf, sizeof(nameBuf)) != 0) {
1522 // TODO: fix this if we expect to have long names
Steve Block3762c312012-01-06 19:20:56 +00001523 ALOGE("ARGH: name too long?\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001524 continue;
1525 }
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001526 //printf("Comparing %s in %s?\n", nameBuf, dirName.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001527 if (dirNameLen == 0 ||
1528 (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 &&
1529 nameBuf[dirNameLen] == '/'))
1530 {
1531 const char* cp;
1532 const char* nextSlash;
1533
1534 cp = nameBuf + dirNameLen;
1535 if (dirNameLen != 0)
1536 cp++; // advance past the '/'
1537
1538 nextSlash = strchr(cp, '/');
1539//xxx this may break if there are bare directory entries
1540 if (nextSlash == NULL) {
1541 /* this is a file in the requested directory */
1542
1543 info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular);
1544
1545 info.setSourceName(
1546 createZipSourceNameLocked(zipName, dirName, info.getFileName()));
1547
1548 contents.add(info);
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001549 //printf("FOUND: file '%s'\n", info.getFileName().string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001550 } else {
1551 /* this is a subdir; add it if we don't already have it*/
1552 String8 subdirName(cp, nextSlash - cp);
1553 size_t j;
1554 size_t N = dirs.size();
1555
1556 for (j = 0; j < N; j++) {
1557 if (subdirName == dirs[j]) {
1558 break;
1559 }
1560 }
1561 if (j == N) {
1562 dirs.add(subdirName);
1563 }
1564
Dianne Hackbornbb9ea302009-05-18 15:22:00 -07001565 //printf("FOUND: dir '%s'\n", subdirName.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001566 }
1567 }
1568 }
1569
Narayan Kamathafd31e02013-12-03 13:16:03 +00001570 pZip->endIteration(iterationCookie);
1571
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001572 /*
1573 * Add the set of unique directories.
1574 */
1575 for (int i = 0; i < (int) dirs.size(); i++) {
1576 info.set(dirs[i], kFileTypeDirectory);
1577 info.setSourceName(
1578 createZipSourceNameLocked(zipName, dirName, info.getFileName()));
1579 contents.add(info);
1580 }
1581
1582 mergeInfoLocked(pMergedInfo, &contents);
1583
1584 return true;
1585}
1586
1587
1588/*
1589 * Merge two vectors of FileInfo.
1590 *
1591 * The merged contents will be stuffed into *pMergedInfo.
1592 *
1593 * If an entry for a file exists in both "pMergedInfo" and "pContents",
1594 * we use the newer "pContents" entry.
1595 */
1596void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1597 const SortedVector<AssetDir::FileInfo>* pContents)
1598{
1599 /*
1600 * Merge what we found in this directory with what we found in
1601 * other places.
1602 *
1603 * Two basic approaches:
1604 * (1) Create a new array that holds the unique values of the two
1605 * arrays.
1606 * (2) Take the elements from pContents and shove them into pMergedInfo.
1607 *
1608 * Because these are vectors of complex objects, moving elements around
1609 * inside the vector requires constructing new objects and allocating
1610 * storage for members. With approach #1, we're always adding to the
1611 * end, whereas with #2 we could be inserting multiple elements at the
1612 * front of the vector. Approach #1 requires a full copy of the
1613 * contents of pMergedInfo, but approach #2 requires the same copy for
1614 * every insertion at the front of pMergedInfo.
1615 *
1616 * (We should probably use a SortedVector interface that allows us to
1617 * just stuff items in, trusting us to maintain the sort order.)
1618 */
1619 SortedVector<AssetDir::FileInfo>* pNewSorted;
1620 int mergeMax, contMax;
1621 int mergeIdx, contIdx;
1622
1623 pNewSorted = new SortedVector<AssetDir::FileInfo>;
1624 mergeMax = pMergedInfo->size();
1625 contMax = pContents->size();
1626 mergeIdx = contIdx = 0;
1627
1628 while (mergeIdx < mergeMax || contIdx < contMax) {
1629 if (mergeIdx == mergeMax) {
1630 /* hit end of "merge" list, copy rest of "contents" */
1631 pNewSorted->add(pContents->itemAt(contIdx));
1632 contIdx++;
1633 } else if (contIdx == contMax) {
1634 /* hit end of "cont" list, copy rest of "merge" */
1635 pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
1636 mergeIdx++;
1637 } else if (pMergedInfo->itemAt(mergeIdx) == pContents->itemAt(contIdx))
1638 {
1639 /* items are identical, add newer and advance both indices */
1640 pNewSorted->add(pContents->itemAt(contIdx));
1641 mergeIdx++;
1642 contIdx++;
1643 } else if (pMergedInfo->itemAt(mergeIdx) < pContents->itemAt(contIdx))
1644 {
1645 /* "merge" is lower, add that one */
1646 pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
1647 mergeIdx++;
1648 } else {
1649 /* "cont" is lower, add that one */
1650 assert(pContents->itemAt(contIdx) < pMergedInfo->itemAt(mergeIdx));
1651 pNewSorted->add(pContents->itemAt(contIdx));
1652 contIdx++;
1653 }
1654 }
1655
1656 /*
1657 * Overwrite the "merged" list with the new stuff.
1658 */
1659 *pMergedInfo = *pNewSorted;
1660 delete pNewSorted;
1661
1662#if 0 // for Vector, rather than SortedVector
1663 int i, j;
1664 for (i = pContents->size() -1; i >= 0; i--) {
1665 bool add = true;
1666
1667 for (j = pMergedInfo->size() -1; j >= 0; j--) {
1668 /* case-sensitive comparisons, to behave like UNIX fs */
1669 if (strcmp(pContents->itemAt(i).mFileName,
1670 pMergedInfo->itemAt(j).mFileName) == 0)
1671 {
1672 /* match, don't add this entry */
1673 add = false;
1674 break;
1675 }
1676 }
1677
1678 if (add)
1679 pMergedInfo->add(pContents->itemAt(i));
1680 }
1681#endif
1682}
1683
1684
1685/*
1686 * Load all files into the file name cache. We want to do this across
1687 * all combinations of { appname, locale, vendor }, performing a recursive
1688 * directory traversal.
1689 *
1690 * This is not the most efficient data structure. Also, gathering the
1691 * information as we needed it (file-by-file or directory-by-directory)
1692 * would be faster. However, on the actual device, 99% of the files will
1693 * live in Zip archives, so this list will be very small. The trouble
1694 * is that we have to check the "loose" files first, so it's important
1695 * that we don't beat the filesystem silly looking for files that aren't
1696 * there.
1697 *
1698 * Note on thread safety: this is the only function that causes updates
1699 * to mCache, and anybody who tries to use it will call here if !mCacheValid,
1700 * so we need to employ a mutex here.
1701 */
1702void AssetManager::loadFileNameCacheLocked(void)
1703{
1704 assert(!mCacheValid);
1705 assert(mCache.size() == 0);
1706
1707#ifdef DO_TIMINGS // need to link against -lrt for this now
1708 DurationTimer timer;
1709 timer.start();
1710#endif
1711
1712 fncScanLocked(&mCache, "");
1713
1714#ifdef DO_TIMINGS
1715 timer.stop();
Steve Block5baa3a62011-12-20 16:23:08 +00001716 ALOGD("Cache scan took %.3fms\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001717 timer.durationUsecs() / 1000.0);
1718#endif
1719
1720#if 0
1721 int i;
1722 printf("CACHED FILE LIST (%d entries):\n", mCache.size());
1723 for (i = 0; i < (int) mCache.size(); i++) {
1724 printf(" %d: (%d) '%s'\n", i,
1725 mCache.itemAt(i).getFileType(),
1726 (const char*) mCache.itemAt(i).getFileName());
1727 }
1728#endif
1729
1730 mCacheValid = true;
1731}
1732
1733/*
1734 * Scan up to 8 versions of the specified directory.
1735 */
1736void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
1737 const char* dirName)
1738{
1739 size_t i = mAssetPaths.size();
1740 while (i > 0) {
1741 i--;
1742 const asset_path& ap = mAssetPaths.itemAt(i);
1743 fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName);
1744 if (mLocale != NULL)
1745 fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName);
1746 if (mVendor != NULL)
1747 fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName);
1748 if (mLocale != NULL && mVendor != NULL)
1749 fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName);
1750 }
1751}
1752
1753/*
1754 * Recursively scan this directory and all subdirs.
1755 *
1756 * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE
1757 * files, and we prepend the extended partial path to the filenames.
1758 */
1759bool AssetManager::fncScanAndMergeDirLocked(
1760 SortedVector<AssetDir::FileInfo>* pMergedInfo,
1761 const asset_path& ap, const char* locale, const char* vendor,
1762 const char* dirName)
1763{
1764 SortedVector<AssetDir::FileInfo>* pContents;
1765 String8 partialPath;
1766 String8 fullPath;
1767
1768 // XXX This is broken -- the filename cache needs to hold the base
1769 // asset path separately from its filename.
Elliott Hughesc367d482013-10-29 13:12:55 -07001770
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001771 partialPath = createPathNameLocked(ap, locale, vendor);
1772 if (dirName[0] != '\0') {
1773 partialPath.appendPath(dirName);
1774 }
1775
1776 fullPath = partialPath;
1777 pContents = scanDirLocked(fullPath);
1778 if (pContents == NULL) {
1779 return false; // directory did not exist
1780 }
1781
1782 /*
1783 * Scan all subdirectories of the current dir, merging what we find
1784 * into "pMergedInfo".
1785 */
1786 for (int i = 0; i < (int) pContents->size(); i++) {
1787 if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) {
1788 String8 subdir(dirName);
1789 subdir.appendPath(pContents->itemAt(i).getFileName());
1790
1791 fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string());
1792 }
1793 }
1794
1795 /*
1796 * To be consistent, we want entries for the root directory. If
1797 * we're the root, add one now.
1798 */
1799 if (dirName[0] == '\0') {
1800 AssetDir::FileInfo tmpInfo;
1801
1802 tmpInfo.set(String8(""), kFileTypeDirectory);
1803 tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor));
1804 pContents->add(tmpInfo);
1805 }
1806
1807 /*
1808 * We want to prepend the extended partial path to every entry in
1809 * "pContents". It's the same value for each entry, so this will
1810 * not change the sorting order of the vector contents.
1811 */
1812 for (int i = 0; i < (int) pContents->size(); i++) {
1813 const AssetDir::FileInfo& info = pContents->itemAt(i);
1814 pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName()));
1815 }
1816
1817 mergeInfoLocked(pMergedInfo, pContents);
1818 return true;
1819}
1820
1821/*
1822 * Trash the cache.
1823 */
1824void AssetManager::purgeFileNameCacheLocked(void)
1825{
1826 mCacheValid = false;
1827 mCache.clear();
1828}
1829
1830/*
1831 * ===========================================================================
1832 * AssetManager::SharedZip
1833 * ===========================================================================
1834 */
1835
1836
1837Mutex AssetManager::SharedZip::gLock;
1838DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen;
1839
1840AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
Dianne Hackborn78c40512009-07-06 11:07:40 -07001841 : mPath(path), mZipFile(NULL), mModWhen(modWhen),
1842 mResourceTableAsset(NULL), mResourceTable(NULL)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001843{
Steve Block6215d3f2012-01-04 20:05:49 +00001844 //ALOGI("Creating SharedZip %p %s\n", this, (const char*)mPath);
Steve Block71f2cf12011-10-20 11:56:00 +01001845 ALOGV("+++ opening zip '%s'\n", mPath.string());
Narayan Kamathafd31e02013-12-03 13:16:03 +00001846 mZipFile = ZipFileRO::open(mPath.string());
1847 if (mZipFile == NULL) {
Steve Block5baa3a62011-12-20 16:23:08 +00001848 ALOGD("failed to open Zip archive '%s'\n", mPath.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001849 }
1850}
1851
1852sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path)
1853{
1854 AutoMutex _l(gLock);
1855 time_t modWhen = getFileModDate(path);
1856 sp<SharedZip> zip = gOpen.valueFor(path).promote();
1857 if (zip != NULL && zip->mModWhen == modWhen) {
1858 return zip;
1859 }
1860 zip = new SharedZip(path, modWhen);
1861 gOpen.add(path, zip);
1862 return zip;
1863
1864}
1865
1866ZipFileRO* AssetManager::SharedZip::getZip()
1867{
1868 return mZipFile;
1869}
1870
1871Asset* AssetManager::SharedZip::getResourceTableAsset()
1872{
Steve Block71f2cf12011-10-20 11:56:00 +01001873 ALOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001874 return mResourceTableAsset;
1875}
1876
1877Asset* AssetManager::SharedZip::setResourceTableAsset(Asset* asset)
1878{
1879 {
1880 AutoMutex _l(gLock);
1881 if (mResourceTableAsset == NULL) {
1882 mResourceTableAsset = asset;
1883 // This is not thread safe the first time it is called, so
1884 // do it here with the global lock held.
1885 asset->getBuffer(true);
1886 return asset;
1887 }
1888 }
1889 delete asset;
1890 return mResourceTableAsset;
1891}
1892
Dianne Hackborn78c40512009-07-06 11:07:40 -07001893ResTable* AssetManager::SharedZip::getResourceTable()
1894{
Steve Block71f2cf12011-10-20 11:56:00 +01001895 ALOGV("Getting from SharedZip %p resource table %p\n", this, mResourceTable);
Dianne Hackborn78c40512009-07-06 11:07:40 -07001896 return mResourceTable;
1897}
1898
1899ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res)
1900{
1901 {
1902 AutoMutex _l(gLock);
1903 if (mResourceTable == NULL) {
1904 mResourceTable = res;
1905 return res;
1906 }
1907 }
1908 delete res;
1909 return mResourceTable;
1910}
1911
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001912bool AssetManager::SharedZip::isUpToDate()
1913{
1914 time_t modWhen = getFileModDate(mPath.string());
1915 return mModWhen == modWhen;
1916}
1917
1918AssetManager::SharedZip::~SharedZip()
1919{
Steve Block6215d3f2012-01-04 20:05:49 +00001920 //ALOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath);
Dianne Hackborn78c40512009-07-06 11:07:40 -07001921 if (mResourceTable != NULL) {
1922 delete mResourceTable;
1923 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001924 if (mResourceTableAsset != NULL) {
1925 delete mResourceTableAsset;
1926 }
1927 if (mZipFile != NULL) {
1928 delete mZipFile;
Steve Block71f2cf12011-10-20 11:56:00 +01001929 ALOGV("Closed '%s'\n", mPath.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001930 }
1931}
1932
1933/*
1934 * ===========================================================================
1935 * AssetManager::ZipSet
1936 * ===========================================================================
1937 */
1938
1939/*
1940 * Constructor.
1941 */
1942AssetManager::ZipSet::ZipSet(void)
1943{
1944}
1945
1946/*
1947 * Destructor. Close any open archives.
1948 */
1949AssetManager::ZipSet::~ZipSet(void)
1950{
1951 size_t N = mZipFile.size();
1952 for (size_t i = 0; i < N; i++)
1953 closeZip(i);
1954}
1955
1956/*
1957 * Close a Zip file and reset the entry.
1958 */
1959void AssetManager::ZipSet::closeZip(int idx)
1960{
1961 mZipFile.editItemAt(idx) = NULL;
1962}
1963
1964
1965/*
1966 * Retrieve the appropriate Zip file from the set.
1967 */
1968ZipFileRO* AssetManager::ZipSet::getZip(const String8& path)
1969{
1970 int idx = getIndex(path);
1971 sp<SharedZip> zip = mZipFile[idx];
1972 if (zip == NULL) {
1973 zip = SharedZip::get(path);
1974 mZipFile.editItemAt(idx) = zip;
1975 }
1976 return zip->getZip();
1977}
1978
Dianne Hackborn78c40512009-07-06 11:07:40 -07001979Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001980{
1981 int idx = getIndex(path);
1982 sp<SharedZip> zip = mZipFile[idx];
1983 if (zip == NULL) {
1984 zip = SharedZip::get(path);
1985 mZipFile.editItemAt(idx) = zip;
1986 }
1987 return zip->getResourceTableAsset();
1988}
1989
Dianne Hackborn78c40512009-07-06 11:07:40 -07001990Asset* AssetManager::ZipSet::setZipResourceTableAsset(const String8& path,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001991 Asset* asset)
1992{
1993 int idx = getIndex(path);
1994 sp<SharedZip> zip = mZipFile[idx];
1995 // doesn't make sense to call before previously accessing.
1996 return zip->setResourceTableAsset(asset);
1997}
1998
Dianne Hackborn78c40512009-07-06 11:07:40 -07001999ResTable* AssetManager::ZipSet::getZipResourceTable(const String8& path)
2000{
2001 int idx = getIndex(path);
2002 sp<SharedZip> zip = mZipFile[idx];
2003 if (zip == NULL) {
2004 zip = SharedZip::get(path);
2005 mZipFile.editItemAt(idx) = zip;
2006 }
2007 return zip->getResourceTable();
2008}
2009
2010ResTable* AssetManager::ZipSet::setZipResourceTable(const String8& path,
2011 ResTable* res)
2012{
2013 int idx = getIndex(path);
2014 sp<SharedZip> zip = mZipFile[idx];
2015 // doesn't make sense to call before previously accessing.
2016 return zip->setResourceTable(res);
2017}
2018
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002019/*
2020 * Generate the partial pathname for the specified archive. The caller
2021 * gets to prepend the asset root directory.
2022 *
2023 * Returns something like "common/en-US-noogle.jar".
2024 */
2025/*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath)
2026{
2027 return String8(zipPath);
2028}
2029
2030bool AssetManager::ZipSet::isUpToDate()
2031{
2032 const size_t N = mZipFile.size();
2033 for (size_t i=0; i<N; i++) {
2034 if (mZipFile[i] != NULL && !mZipFile[i]->isUpToDate()) {
2035 return false;
2036 }
2037 }
2038 return true;
2039}
2040
2041/*
2042 * Compute the zip file's index.
2043 *
2044 * "appName", "locale", and "vendor" should be set to NULL to indicate the
2045 * default directory.
2046 */
2047int AssetManager::ZipSet::getIndex(const String8& zip) const
2048{
2049 const size_t N = mZipPath.size();
2050 for (size_t i=0; i<N; i++) {
2051 if (mZipPath[i] == zip) {
2052 return i;
2053 }
2054 }
2055
2056 mZipPath.add(zip);
2057 mZipFile.add(NULL);
2058
2059 return mZipPath.size()-1;
2060}