1 package org.robolectric.res.android; 2 3 import static org.robolectric.res.android.Asset.toIntExact; 4 import static org.robolectric.res.android.CppAssetManager.FileType.kFileTypeDirectory; 5 import static org.robolectric.res.android.Util.ALOGD; 6 import static org.robolectric.res.android.Util.ALOGE; 7 import static org.robolectric.res.android.Util.ALOGI; 8 import static org.robolectric.res.android.Util.ALOGV; 9 import static org.robolectric.res.android.Util.ALOGW; 10 import static org.robolectric.res.android.Util.ATRACE_CALL; 11 import static org.robolectric.res.android.Util.LOG_FATAL_IF; 12 import static org.robolectric.res.android.Util.isTruthy; 13 14 import com.google.common.annotations.VisibleForTesting; 15 import com.google.common.base.Preconditions; 16 import java.io.File; 17 import java.io.IOException; 18 import java.lang.ref.WeakReference; 19 import java.nio.file.Files; 20 import java.nio.file.Path; 21 import java.nio.file.Paths; 22 import java.util.ArrayList; 23 import java.util.Enumeration; 24 import java.util.HashMap; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Objects; 28 import java.util.zip.ZipEntry; 29 import javax.annotation.Nullable; 30 import org.robolectric.res.Fs; 31 import org.robolectric.res.android.Asset.AccessMode; 32 import org.robolectric.res.android.AssetDir.FileInfo; 33 import org.robolectric.res.android.ZipFileRO.ZipEntryRO; 34 import org.robolectric.util.PerfStatsCollector; 35 36 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/AssetManager.cpp 37 @SuppressWarnings("NewApi") 38 public class CppAssetManager { 39 40 private static final boolean kIsDebug = false; 41 42 enum FileType { 43 kFileTypeUnknown, 44 kFileTypeNonexistent, // i.e. ENOENT 45 kFileTypeRegular, 46 kFileTypeDirectory, 47 kFileTypeCharDev, 48 kFileTypeBlockDev, 49 kFileTypeFifo, 50 kFileTypeSymlink, 51 kFileTypeSocket, 52 } 53 54 // transliterated from 55 // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/androidfw/include/androidfw/AssetManager.h 56 private static class asset_path { 57 // asset_path() : path(""), type(kFileTypeRegular), idmap(""), 58 // isSystemOverlay(false), isSystemAsset(false) {} 59 60 asset_path()61 public asset_path() { 62 this(new String8(), FileType.kFileTypeRegular, new String8(""), false, false); 63 } 64 asset_path(String8 path, FileType fileType, String8 idmap, boolean isSystemOverlay, boolean isSystemAsset)65 public asset_path(String8 path, FileType fileType, String8 idmap, 66 boolean isSystemOverlay, 67 boolean isSystemAsset) { 68 this.path = path; 69 this.type = fileType; 70 this.idmap = idmap; 71 this.isSystemOverlay = isSystemOverlay; 72 this.isSystemAsset = isSystemAsset; 73 } 74 75 String8 path; 76 FileType type; 77 String8 idmap; 78 boolean isSystemOverlay; 79 boolean isSystemAsset; 80 81 @Override toString()82 public String toString() { 83 return "asset_path{" + 84 "path=" + path + 85 ", type=" + type + 86 ", idmap='" + idmap + '\'' + 87 ", isSystemOverlay=" + isSystemOverlay + 88 ", isSystemAsset=" + isSystemAsset + 89 '}'; 90 } 91 } 92 93 private final Object mLock = new Object(); 94 95 // unlike AssetManager.cpp, this is shared between CppAssetManager instances, and is used 96 // to cache ResTables between tests. 97 private static final ZipSet mZipSet = new ZipSet(); 98 99 private final List<asset_path> mAssetPaths = new ArrayList<>(); 100 private String mLocale; 101 102 private ResTable mResources; 103 private ResTable_config mConfig = new ResTable_config(); 104 105 106 // static final boolean kIsDebug = false; 107 // 108 static final String kAssetsRoot = "assets"; 109 static final String kAppZipName = null; //"classes.jar"; 110 static final String kSystemAssets = "android.jar"; 111 // static final char* kResourceCache = "resource-cache"; 112 // 113 static final String kExcludeExtension = ".EXCLUDE"; 114 // 115 116 // static Asset final kExcludedAsset = (Asset*) 0xd000000d; 117 static final Asset kExcludedAsset = Asset.EXCLUDED_ASSET; 118 119 120 static volatile int gCount = 0; 121 122 // final char* RESOURCES_FILENAME = "resources.arsc"; 123 // final char* IDMAP_BIN = "/system/bin/idmap"; 124 // final char* OVERLAY_DIR = "/vendor/overlay"; 125 // final char* OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme"; 126 // final char* TARGET_PACKAGE_NAME = "android"; 127 // final char* TARGET_APK_PATH = "/system/framework/framework-res.apk"; 128 // final char* IDMAP_DIR = "/data/resource-cache"; 129 // 130 // namespace { 131 // idmapPathForPackagePath(final String8 pkgPath)132 String8 idmapPathForPackagePath(final String8 pkgPath) { 133 // TODO: implement this? 134 return pkgPath; 135 // const char* root = getenv("ANDROID_DATA"); 136 // LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set"); 137 // String8 path(root); 138 // path.appendPath(kResourceCache); 139 // char buf[256]; // 256 chars should be enough for anyone... 140 // strncpy(buf, pkgPath.string(), 255); 141 // buf[255] = '\0'; 142 // char* filename = buf; 143 // while (*filename && *filename == '/') { 144 // ++filename; 145 // } 146 // char* p = filename; 147 // while (*p) { 148 // if (*p == '/') { 149 // *p = '@'; 150 // } 151 // ++p; 152 // } 153 // path.appendPath(filename); 154 // path.append("@idmap"); 155 // return path; 156 } 157 // 158 // /* 159 // * Like strdup(), but uses C++ "new" operator instead of malloc. 160 // */ 161 // static char* strdupNew(final char* str) { 162 // char* newStr; 163 // int len; 164 // 165 // if (str == null) 166 // return null; 167 // 168 // len = strlen(str); 169 // newStr = new char[len+1]; 170 // memcpy(newStr, str, len+1); 171 // 172 // return newStr; 173 // } 174 // 175 // } // namespace 176 // 177 // /* 178 // * =========================================================================== 179 // * AssetManager 180 // * =========================================================================== 181 // */ 182 getGlobalCount()183 public static int getGlobalCount() { 184 return gCount; 185 } 186 187 // AssetManager() : 188 // mLocale(null), mResources(null), mConfig(new ResTable_config) { 189 // int count = android_atomic_inc(&gCount) + 1; 190 // if (kIsDebug) { 191 // ALOGI("Creating AssetManager %s #%d\n", this, count); 192 // } 193 // memset(mConfig, 0, sizeof(ResTable_config)); 194 // } 195 // 196 // ~AssetManager() { 197 // int count = android_atomic_dec(&gCount); 198 // if (kIsDebug) { 199 // ALOGI("Destroying AssetManager in %s #%d\n", this, count); 200 // } else { 201 // ALOGI("Destroying AssetManager in %s #%d\n", this, count); 202 // } 203 // // Manually close any fd paths for which we have not yet opened their zip (which 204 // // will take ownership of the fd and close it when done). 205 // for (size_t i=0; i<mAssetPaths.size(); i++) { 206 // ALOGV("Cleaning path #%d: fd=%d, zip=%p", (int)i, mAssetPaths[i].rawFd, 207 // mAssetPaths[i].zip.get()); 208 // if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) { 209 // close(mAssetPaths[i].rawFd); 210 // } 211 // } 212 // 213 // delete mConfig; 214 // delete mResources; 215 // 216 // // don't have a String class yet, so make sure we clean up 217 // delete[] mLocale; 218 // } 219 addAssetPath(String8 path, Ref<Integer> cookie, boolean appAsLib)220 public boolean addAssetPath(String8 path, Ref<Integer> cookie, boolean appAsLib) { 221 return addAssetPath(path, cookie, appAsLib, false); 222 } 223 addAssetPath( final String8 path, @Nullable Ref<Integer> cookie, boolean appAsLib, boolean isSystemAsset)224 public boolean addAssetPath( 225 final String8 path, @Nullable Ref<Integer> cookie, boolean appAsLib, boolean isSystemAsset) { 226 synchronized (mLock) { 227 228 asset_path ap = new asset_path(); 229 230 String8 realPath = path; 231 if (kAppZipName != null) { 232 realPath.appendPath(kAppZipName); 233 } 234 ap.type = getFileType(realPath.string()); 235 if (ap.type == FileType.kFileTypeRegular) { 236 ap.path = realPath; 237 } else { 238 ap.path = path; 239 ap.type = getFileType(path.string()); 240 if (ap.type != kFileTypeDirectory && ap.type != FileType.kFileTypeRegular) { 241 ALOGW("Asset path %s is neither a directory nor file (type=%s).", 242 path.toString(), ap.type.name()); 243 return false; 244 } 245 } 246 247 // Skip if we have it already. 248 for (int i = 0; i < mAssetPaths.size(); i++) { 249 if (mAssetPaths.get(i).path.equals(ap.path)) { 250 if (cookie != null) { 251 cookie.set(i + 1); 252 } 253 return true; 254 } 255 } 256 257 ALOGV("In %s Asset %s path: %s", this, 258 ap.type.name(), ap.path.toString()); 259 260 ap.isSystemAsset = isSystemAsset; 261 /*int apPos =*/ mAssetPaths.add(ap); 262 263 // new paths are always added at the end 264 if (cookie != null) { 265 cookie.set(mAssetPaths.size()); 266 } 267 268 // TODO: implement this? 269 //#ifdef __ANDROID__ 270 // Load overlays, if any 271 //asset_path oap; 272 //for (int idx = 0; mZipSet.getOverlay(ap.path, idx, & oap) 273 // ; idx++){ 274 // oap.isSystemAsset = isSystemAsset; 275 // mAssetPaths.add(oap); 276 // } 277 //#endif 278 279 if (mResources != null) { 280 // appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib); 281 appendPathToResTable(ap, appAsLib); 282 } 283 284 return true; 285 } 286 } 287 288 // 289 // boolean addOverlayPath(final String8 packagePath, Ref<Integer> cookie) 290 // { 291 // final String8 idmapPath = idmapPathForPackagePath(packagePath); 292 // 293 // synchronized (mLock) { 294 // 295 // for (int i = 0; i < mAssetPaths.size(); ++i) { 296 // if (mAssetPaths.get(i).idmap.equals(idmapPath)) { 297 // cookie.set(i + 1); 298 // return true; 299 // } 300 // } 301 // 302 // Asset idmap = null; 303 // if ((idmap = openAssetFromFileLocked(idmapPath, Asset.AccessMode.ACCESS_BUFFER)) == 304 // null) { 305 // ALOGW("failed to open idmap file %s\n", idmapPath.string()); 306 // return false; 307 // } 308 // 309 // String8 targetPath; 310 // String8 overlayPath; 311 // if (!ResTable.getIdmapInfo(idmap.getBuffer(false), idmap.getLength(), 312 // null, null, null, & targetPath, &overlayPath)){ 313 // ALOGW("failed to read idmap file %s\n", idmapPath.string()); 314 // // delete idmap; 315 // return false; 316 // } 317 // // delete idmap; 318 // 319 // if (overlayPath != packagePath) { 320 // ALOGW("idmap file %s inconcistent: expected path %s does not match actual path %s\n", 321 // idmapPath.string(), packagePath.string(), overlayPath.string()); 322 // return false; 323 // } 324 // if (access(targetPath.string(), R_OK) != 0) { 325 // ALOGW("failed to access file %s: %s\n", targetPath.string(), strerror(errno)); 326 // return false; 327 // } 328 // if (access(idmapPath.string(), R_OK) != 0) { 329 // ALOGW("failed to access file %s: %s\n", idmapPath.string(), strerror(errno)); 330 // return false; 331 // } 332 // if (access(overlayPath.string(), R_OK) != 0) { 333 // ALOGW("failed to access file %s: %s\n", overlayPath.string(), strerror(errno)); 334 // return false; 335 // } 336 // 337 // asset_path oap; 338 // oap.path = overlayPath; 339 // oap.type = .getFileType(overlayPath.string()); 340 // oap.idmap = idmapPath; 341 // #if 0 342 // ALOGD("Overlay added: targetPath=%s overlayPath=%s idmapPath=%s\n", 343 // targetPath.string(), overlayPath.string(), idmapPath.string()); 344 // #endif 345 // mAssetPaths.add(oap); 346 // *cookie = static_cast <int>(mAssetPaths.size()); 347 // 348 // if (mResources != null) { 349 // appendPathToResTable(oap); 350 // } 351 // 352 // return true; 353 // } 354 // } 355 // 356 // boolean createIdmap(final char* targetApkPath, final char* overlayApkPath, 357 // uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, int* outSize) 358 // { 359 // AutoMutex _l(mLock); 360 // final String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) }; 361 // Asset* assets[2] = {null, null}; 362 // boolean ret = false; 363 // { 364 // ResTable tables[2]; 365 // 366 // for (int i = 0; i < 2; ++i) { 367 // asset_path ap; 368 // ap.type = kFileTypeRegular; 369 // ap.path = paths[i]; 370 // assets[i] = openNonAssetInPathLocked("resources.arsc", 371 // Asset.ACCESS_BUFFER, ap); 372 // if (assets[i] == null) { 373 // ALOGW("failed to find resources.arsc in %s\n", ap.path.string()); 374 // goto exit; 375 // } 376 // if (tables[i].add(assets[i]) != NO_ERROR) { 377 // ALOGW("failed to add %s to resource table", paths[i].string()); 378 // goto exit; 379 // } 380 // } 381 // ret = tables[0].createIdmap(tables[1], targetCrc, overlayCrc, 382 // targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR; 383 // } 384 // 385 // exit: 386 // delete assets[0]; 387 // delete assets[1]; 388 // return ret; 389 // } 390 // addDefaultAssets(Path systemAssetsPath)391 public boolean addDefaultAssets(Path systemAssetsPath) { 392 return addDefaultAssets(Fs.externalize(systemAssetsPath)); 393 } 394 addDefaultAssets(String systemAssetsPath)395 public boolean addDefaultAssets(String systemAssetsPath) { 396 String8 path = new String8(systemAssetsPath); 397 return addAssetPath(path, null, false /* appAsLib */, true /* isSystemAsset */); 398 } 399 // 400 // int nextAssetPath(final int cookie) final 401 // { 402 // AutoMutex _l(mLock); 403 // final int next = static_cast<int>(cookie) + 1; 404 // return next > mAssetPaths.size() ? -1 : next; 405 // } 406 // 407 // String8 getAssetPath(final int cookie) final 408 // { 409 // AutoMutex _l(mLock); 410 // final int which = static_cast<int>(cookie) - 1; 411 // if (which < mAssetPaths.size()) { 412 // return mAssetPaths[which].path; 413 // } 414 // return String8(); 415 // } 416 setLocaleLocked(final String locale)417 void setLocaleLocked(final String locale) { 418 // if (mLocale != null) { 419 // delete[] mLocale; 420 // } 421 422 mLocale = /*strdupNew*/ locale; 423 updateResourceParamsLocked(); 424 } 425 setConfiguration(final ResTable_config config, final String locale)426 public void setConfiguration(final ResTable_config config, final String locale) { 427 synchronized (mLock) { 428 mConfig = config; 429 if (isTruthy(locale)) { 430 setLocaleLocked(locale); 431 } else { 432 if (config.language[0] != 0) { 433 // byte[] spec = new byte[RESTABLE_MAX_LOCALE_LEN]; 434 String spec = config.getBcp47Locale(false); 435 setLocaleLocked(spec); 436 } else { 437 updateResourceParamsLocked(); 438 } 439 } 440 } 441 } 442 443 @VisibleForTesting getConfiguration(Ref<ResTable_config> outConfig)444 public void getConfiguration(Ref<ResTable_config> outConfig) { 445 synchronized (mLock) { 446 outConfig.set(mConfig); 447 } 448 } 449 450 /* 451 * Open an asset. 452 * 453 * The data could be in any asset path. Each asset path could be: 454 * - A directory on disk. 455 * - A Zip archive, uncompressed or compressed. 456 * 457 * If the file is in a directory, it could have a .gz suffix, meaning it is compressed. 458 * 459 * We should probably reject requests for "illegal" filenames, e.g. those 460 * with illegal characters or "../" backward relative paths. 461 */ open(final String fileName, AccessMode mode)462 public Asset open(final String fileName, AccessMode mode) { 463 synchronized (mLock) { 464 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 465 466 String8 assetName = new String8(kAssetsRoot); 467 assetName.appendPath(fileName); 468 /* 469 * For each top-level asset path, search for the asset. 470 */ 471 int i = mAssetPaths.size(); 472 while (i > 0) { 473 i--; 474 ALOGV("Looking for asset '%s' in '%s'\n", 475 assetName.string(), mAssetPaths.get(i).path.string()); 476 Asset pAsset = openNonAssetInPathLocked(assetName.string(), mode, 477 mAssetPaths.get(i)); 478 if (pAsset != null) { 479 return Objects.equals(pAsset, kExcludedAsset) ? null : pAsset; 480 } 481 } 482 483 return null; 484 } 485 } 486 487 /* 488 * Open a non-asset file as if it were an asset. 489 * 490 * The "fileName" is the partial path starting from the application name. 491 */ openNonAsset(final String fileName, AccessMode mode, Ref<Integer> outCookie)492 public Asset openNonAsset(final String fileName, AccessMode mode, Ref<Integer> outCookie) { 493 synchronized (mLock) { 494 // AutoMutex _l(mLock); 495 496 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 497 498 /* 499 * For each top-level asset path, search for the asset. 500 */ 501 502 int i = mAssetPaths.size(); 503 while (i > 0) { 504 i--; 505 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, 506 mAssetPaths.get(i).path.string()); 507 Asset pAsset = openNonAssetInPathLocked( 508 fileName, mode, mAssetPaths.get(i)); 509 if (pAsset != null) { 510 if (outCookie != null) { 511 outCookie.set(i + 1); 512 } 513 return pAsset != kExcludedAsset ? pAsset : null; 514 } 515 } 516 517 return null; 518 } 519 } 520 openNonAsset(final int cookie, final String fileName, AccessMode mode)521 public Asset openNonAsset(final int cookie, final String fileName, AccessMode mode) { 522 final int which = cookie - 1; 523 524 synchronized (mLock) { 525 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 526 527 if (which < mAssetPaths.size()) { 528 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, 529 mAssetPaths.get(which).path.string()); 530 Asset pAsset = openNonAssetInPathLocked( 531 fileName, mode, mAssetPaths.get(which)); 532 if (pAsset != null) { 533 return pAsset != kExcludedAsset ? pAsset : null; 534 } 535 } 536 537 return null; 538 } 539 } 540 541 /* 542 * Get the type of a file 543 */ getFileType(final String fileName)544 FileType getFileType(final String fileName) { 545 // deviate from Android CPP implementation here. Assume fileName is a complete path 546 // rather than limited to just asset namespace 547 File assetFile = new File(fileName); 548 if (!assetFile.exists()) { 549 return FileType.kFileTypeNonexistent; 550 } else if (assetFile.isFile()) { 551 return FileType.kFileTypeRegular; 552 } else if (assetFile.isDirectory()) { 553 return kFileTypeDirectory; 554 } 555 return FileType.kFileTypeNonexistent; 556 // Asset pAsset = null; 557 // 558 // /* 559 // * Open the asset. This is less efficient than simply finding the 560 // * file, but it's not too bad (we don't uncompress or mmap data until 561 // * the first read() call). 562 // */ 563 // pAsset = open(fileName, Asset.AccessMode.ACCESS_STREAMING); 564 // // delete pAsset; 565 // 566 // if (pAsset == null) { 567 // return FileType.kFileTypeNonexistent; 568 // } else { 569 // return FileType.kFileTypeRegular; 570 // } 571 } 572 appendPathToResTable(final asset_path ap, boolean appAsLib)573 boolean appendPathToResTable(final asset_path ap, boolean appAsLib) { 574 return PerfStatsCollector.getInstance() 575 .measure( 576 "load binary " + (ap.isSystemAsset ? "framework" : "app") + " resources", 577 () -> appendPathToResTable_measured(ap, appAsLib)); 578 } 579 appendPathToResTable_measured(final asset_path ap, boolean appAsLib)580 boolean appendPathToResTable_measured(final asset_path ap, boolean appAsLib) { 581 // TODO: properly handle reading system resources 582 // if (!ap.isSystemAsset) { 583 // URL resource = getClass().getResource("/resources.ap_"); // todo get this from asset_path 584 // // System.out.println("Reading ARSC file from " + resource); 585 // LOG_FATAL_IF(resource == null, "Could not find resources.ap_"); 586 // try { 587 // ZipFile zipFile = new ZipFile(resource.getFile()); 588 // ZipEntry arscEntry = zipFile.getEntry("resources.arsc"); 589 // InputStream inputStream = zipFile.getInputStream(arscEntry); 590 // mResources.add(inputStream, mResources.getTableCount() + 1); 591 // } catch (IOException e) { 592 // throw new RuntimeException(e); 593 // } 594 // } else { 595 // try { 596 // ZipFile zipFile = new ZipFile(ap.path.string()); 597 // ZipEntry arscEntry = zipFile.getEntry("resources.arsc"); 598 // InputStream inputStream = zipFile.getInputStream(arscEntry); 599 // mResources.add(inputStream, mResources.getTableCount() + 1); 600 // } catch (IOException e) { 601 // e.printStackTrace(); 602 // } 603 // } 604 // return false; 605 606 // skip those ap's that correspond to system overlays 607 if (ap.isSystemOverlay) { 608 return true; 609 } 610 611 Asset ass = null; 612 ResTable sharedRes = null; 613 boolean shared = true; 614 boolean onlyEmptyResources = true; 615 // ATRACE_NAME(ap.path.string()); 616 Asset idmap = openIdmapLocked(ap); 617 int nextEntryIdx = mResources.getTableCount(); 618 ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); 619 if (ap.type != kFileTypeDirectory /*&& ap.rawFd < 0*/) { 620 if (nextEntryIdx == 0) { 621 // The first item is typically the framework resources, 622 // which we want to avoid parsing every time. 623 sharedRes = mZipSet.getZipResourceTable(ap.path); 624 if (sharedRes != null) { 625 // skip ahead the number of system overlay packages preloaded 626 nextEntryIdx = sharedRes.getTableCount(); 627 } 628 } 629 if (sharedRes == null) { 630 ass = mZipSet.getZipResourceTableAsset(ap.path); 631 if (ass == null) { 632 ALOGV("loading resource table %s\n", ap.path.string()); 633 ass = openNonAssetInPathLocked("resources.arsc", 634 AccessMode.ACCESS_BUFFER, 635 ap); 636 if (ass != null && ass != kExcludedAsset) { 637 ass = mZipSet.setZipResourceTableAsset(ap.path, ass); 638 } 639 } 640 641 if (nextEntryIdx == 0 && ass != null) { 642 // If this is the first resource table in the asset 643 // manager, then we are going to cache it so that we 644 // can quickly copy it out for others. 645 ALOGV("Creating shared resources for %s", ap.path.string()); 646 sharedRes = new ResTable(); 647 sharedRes.add(ass, idmap, nextEntryIdx + 1, false, false, false); 648 // #ifdef __ANDROID__ 649 // final char* data = getenv("ANDROID_DATA"); 650 // LOG_ALWAYS_FATAL_IF(data == null, "ANDROID_DATA not set"); 651 // String8 overlaysListPath(data); 652 // overlaysListPath.appendPath(kResourceCache); 653 // overlaysListPath.appendPath("overlays.list"); 654 // addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx); 655 // #endif 656 sharedRes = mZipSet.setZipResourceTable(ap.path, sharedRes); 657 } 658 } 659 } else { 660 ALOGV("loading resource table %s\n", ap.path.string()); 661 ass = openNonAssetInPathLocked("resources.arsc", 662 AccessMode.ACCESS_BUFFER, 663 ap); 664 shared = false; 665 } 666 667 if ((ass != null || sharedRes != null) && ass != kExcludedAsset) { 668 ALOGV("Installing resource asset %s in to table %s\n", ass, mResources); 669 if (sharedRes != null) { 670 ALOGV("Copying existing resources for %s", ap.path.string()); 671 mResources.add(sharedRes, ap.isSystemAsset); 672 } else { 673 ALOGV("Parsing resources for %s", ap.path.string()); 674 mResources.add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset); 675 } 676 onlyEmptyResources = false; 677 678 // if (!shared) { 679 // delete ass; 680 // } 681 } else { 682 ALOGV("Installing empty resources in to table %s\n", mResources); 683 mResources.addEmpty(nextEntryIdx + 1); 684 } 685 686 // if (idmap != null) { 687 // delete idmap; 688 // } 689 return onlyEmptyResources; 690 } 691 getResTable(boolean required)692 final ResTable getResTable(boolean required) { 693 ResTable rt = mResources; 694 if (isTruthy(rt)) { 695 return rt; 696 } 697 698 // Iterate through all asset packages, collecting resources from each. 699 700 synchronized (mLock) { 701 if (mResources != null) { 702 return mResources; 703 } 704 705 if (required) { 706 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 707 } 708 709 PerfStatsCollector.getInstance().measure("load binary resources", () -> { 710 mResources = new ResTable(); 711 updateResourceParamsLocked(); 712 713 boolean onlyEmptyResources = true; 714 final int N = mAssetPaths.size(); 715 for (int i = 0; i < N; i++) { 716 boolean empty = appendPathToResTable(mAssetPaths.get(i), false); 717 onlyEmptyResources = onlyEmptyResources && empty; 718 } 719 720 if (required && onlyEmptyResources) { 721 ALOGW("Unable to find resources file resources.arsc"); 722 // delete mResources; 723 mResources = null; 724 } 725 }); 726 727 return mResources; 728 } 729 } 730 updateResourceParamsLocked()731 void updateResourceParamsLocked() { 732 ATRACE_CALL(); 733 ResTable res = mResources; 734 if (!isTruthy(res)) { 735 return; 736 } 737 738 if (isTruthy(mLocale)) { 739 mConfig.setBcp47Locale(mLocale); 740 } else { 741 mConfig.clearLocale(); 742 } 743 744 res.setParameters(mConfig); 745 } 746 openIdmapLocked(asset_path ap)747 Asset openIdmapLocked(asset_path ap) { 748 Asset ass = null; 749 if (ap.idmap.length() != 0) { 750 ass = openAssetFromFileLocked(ap.idmap, AccessMode.ACCESS_BUFFER); 751 if (isTruthy(ass)) { 752 ALOGV("loading idmap %s\n", ap.idmap.string()); 753 } else { 754 ALOGW("failed to load idmap %s\n", ap.idmap.string()); 755 } 756 } 757 return ass; 758 } 759 760 // void addSystemOverlays(final char* pathOverlaysList, 761 // final String8& targetPackagePath, ResTable* sharedRes, int offset) final 762 // { 763 // FILE* fin = fopen(pathOverlaysList, "r"); 764 // if (fin == null) { 765 // return; 766 // } 767 // 768 // #ifndef _WIN32 769 // if (TEMP_FAILURE_RETRY(flock(fileno(fin), LOCK_SH)) != 0) { 770 // fclose(fin); 771 // return; 772 // } 773 // #endif 774 // char buf[1024]; 775 // while (fgets(buf, sizeof(buf), fin)) { 776 // // format of each line: 777 // // <path to apk><space><path to idmap><newline> 778 // char* space = strchr(buf, ' '); 779 // char* newline = strchr(buf, '\n'); 780 // asset_path oap; 781 // 782 // if (space == null || newline == null || newline < space) { 783 // continue; 784 // } 785 // 786 // oap.path = String8(buf, space - buf); 787 // oap.type = kFileTypeRegular; 788 // oap.idmap = String8(space + 1, newline - space - 1); 789 // oap.isSystemOverlay = true; 790 // 791 // Asset* oass = final_cast<AssetManager*>(this). 792 // openNonAssetInPathLocked("resources.arsc", 793 // Asset.ACCESS_BUFFER, 794 // oap); 795 // 796 // if (oass != null) { 797 // Asset* oidmap = openIdmapLocked(oap); 798 // offset++; 799 // sharedRes.add(oass, oidmap, offset + 1, false); 800 // final_cast<AssetManager*>(this).mAssetPaths.add(oap); 801 // final_cast<AssetManager*>(this).mZipSet.addOverlay(targetPackagePath, oap); 802 // delete oidmap; 803 // } 804 // } 805 // 806 // #ifndef _WIN32 807 // TEMP_FAILURE_RETRY(flock(fileno(fin), LOCK_UN)); 808 // #endif 809 // fclose(fin); 810 // } 811 getResources()812 public final ResTable getResources() { 813 return getResources(true); 814 } 815 getResources(boolean required)816 final ResTable getResources(boolean required) { 817 final ResTable rt = getResTable(required); 818 return rt; 819 } 820 821 // boolean isUpToDate() 822 // { 823 // AutoMutex _l(mLock); 824 // return mZipSet.isUpToDate(); 825 // } 826 // 827 // void getLocales(Vector<String8>* locales, boolean includeSystemLocales) final 828 // { 829 // ResTable* res = mResources; 830 // if (res != null) { 831 // res.getLocales(locales, includeSystemLocales, true /* mergeEquivalentLangs */); 832 // } 833 // } 834 // 835 /* 836 * Open a non-asset file as if it were an asset, searching for it in the 837 * specified app. 838 * 839 * Pass in a null values for "appName" if the common app directory should 840 * be used. 841 */ openNonAssetInPathLocked(final String fileName, AccessMode mode, final asset_path ap)842 static Asset openNonAssetInPathLocked(final String fileName, AccessMode mode, 843 final asset_path ap) { 844 Asset pAsset = null; 845 846 /* look at the filesystem on disk */ 847 if (ap.type == kFileTypeDirectory) { 848 String8 path = new String8(ap.path); 849 path.appendPath(fileName); 850 851 pAsset = openAssetFromFileLocked(path, mode); 852 853 if (pAsset == null) { 854 /* try again, this time with ".gz" */ 855 path.append(".gz"); 856 pAsset = openAssetFromFileLocked(path, mode); 857 } 858 859 if (pAsset != null) { 860 //printf("FOUND NA '%s' on disk\n", fileName); 861 pAsset.setAssetSource(path); 862 } 863 864 /* look inside the zip file */ 865 } else { 866 String8 path = new String8(fileName); 867 868 /* check the appropriate Zip file */ 869 ZipFileRO pZip = getZipFileLocked(ap); 870 if (pZip != null) { 871 //printf("GOT zip, checking NA '%s'\n", (final char*) path); 872 ZipEntryRO entry = pZip.findEntryByName(path.string()); 873 if (entry != null) { 874 //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon); 875 pAsset = openAssetFromZipLocked(pZip, entry, mode, path); 876 pZip.releaseEntry(entry); 877 } 878 } 879 880 if (pAsset != null) { 881 /* create a "source" name, for debug/display */ 882 pAsset.setAssetSource( 883 createZipSourceNameLocked(ap.path, new String8(), new String8(fileName))); 884 } 885 } 886 887 return pAsset; 888 } 889 890 /* 891 * Create a "source name" for a file from a Zip archive. 892 */ createZipSourceNameLocked(final String8 zipFileName, final String8 dirName, final String8 fileName)893 static String8 createZipSourceNameLocked(final String8 zipFileName, 894 final String8 dirName, final String8 fileName) { 895 String8 sourceName = new String8("zip:"); 896 sourceName.append(zipFileName.string()); 897 sourceName.append(":"); 898 if (dirName.length() > 0) { 899 sourceName.appendPath(dirName.string()); 900 } 901 sourceName.appendPath(fileName.string()); 902 return sourceName; 903 } 904 905 /* 906 * Create a path to a loose asset (asset-base/app/rootDir). 907 */ createPathNameLocked(final asset_path ap, final String rootDir)908 static String8 createPathNameLocked(final asset_path ap, final String rootDir) { 909 String8 path = new String8(ap.path); 910 if (rootDir != null) { 911 path.appendPath(rootDir); 912 } 913 return path; 914 } 915 916 /* 917 * Return a pointer to one of our open Zip archives. Returns null if no 918 * matching Zip file exists. 919 */ getZipFileLocked(final asset_path ap)920 static ZipFileRO getZipFileLocked(final asset_path ap) { 921 ALOGV("getZipFileLocked() in %s\n", CppAssetManager.class); 922 923 return mZipSet.getZip(ap.path.string()); 924 } 925 926 /* 927 * Try to open an asset from a file on disk. 928 * 929 * If the file is compressed with gzip, we seek to the start of the 930 * deflated data and pass that in (just like we would for a Zip archive). 931 * 932 * For uncompressed data, we may already have an mmap()ed version sitting 933 * around. If so, we want to hand that to the Asset instead. 934 * 935 * This returns null if the file doesn't exist, couldn't be opened, or 936 * claims to be a ".gz" but isn't. 937 */ openAssetFromFileLocked(final String8 pathName, AccessMode mode)938 static Asset openAssetFromFileLocked(final String8 pathName, 939 AccessMode mode) { 940 Asset pAsset = null; 941 942 if (pathName.getPathExtension().toLowerCase().equals(".gz")) { 943 //printf("TRYING '%s'\n", (final char*) pathName); 944 pAsset = Asset.createFromCompressedFile(pathName.string(), mode); 945 } else { 946 //printf("TRYING '%s'\n", (final char*) pathName); 947 pAsset = Asset.createFromFile(pathName.string(), mode); 948 } 949 950 return pAsset; 951 } 952 953 /* 954 * Given an entry in a Zip archive, create a new Asset object. 955 * 956 * If the entry is uncompressed, we may want to create or share a 957 * slice of shared memory. 958 */ openAssetFromZipLocked(final ZipFileRO pZipFile, final ZipEntryRO entry, AccessMode mode, final String8 entryName)959 static Asset openAssetFromZipLocked(final ZipFileRO pZipFile, 960 final ZipEntryRO entry, AccessMode mode, final String8 entryName) { 961 Asset pAsset = null; 962 963 // TODO: look for previously-created shared memory slice? 964 final Ref<Short> method = new Ref<>((short) 0); 965 final Ref<Long> uncompressedLen = new Ref<>(0L); 966 967 //printf("USING Zip '%s'\n", pEntry.getFileName()); 968 969 if (!pZipFile.getEntryInfo(entry, method, uncompressedLen, null, null, 970 null, null)) { 971 ALOGW("getEntryInfo failed\n"); 972 return null; 973 } 974 975 //return Asset.createFromZipEntry(pZipFile, entry, entryName); 976 FileMap dataMap = pZipFile.createEntryFileMap(entry); 977 // if (dataMap == null) { 978 // ALOGW("create map from entry failed\n"); 979 // return null; 980 // } 981 // 982 if (method.get() == ZipFileRO.kCompressStored) { 983 pAsset = Asset.createFromUncompressedMap(dataMap, mode); 984 ALOGV("Opened uncompressed entry %s in zip %s mode %s: %s", entryName.string(), 985 pZipFile.mFileName, mode, pAsset); 986 } else { 987 pAsset = Asset.createFromCompressedMap(dataMap, toIntExact(uncompressedLen.get()), mode); 988 ALOGV("Opened compressed entry %s in zip %s mode %s: %s", entryName.string(), 989 pZipFile.mFileName, mode, pAsset); 990 } 991 if (pAsset == null) { 992 /* unexpected */ 993 ALOGW("create from segment failed\n"); 994 } 995 996 return pAsset; 997 } 998 999 /* 1000 * Open a directory in the asset namespace. 1001 * 1002 * An "asset directory" is simply the combination of all asset paths' "assets/" directories. 1003 * 1004 * Pass in "" for the root dir. 1005 */ openDir(final String dirName)1006 public AssetDir openDir(final String dirName) { 1007 synchronized (mLock) { 1008 AssetDir pDir; 1009 final Ref<SortedVector<AssetDir.FileInfo>> pMergedInfo; 1010 1011 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 1012 Preconditions.checkNotNull(dirName); 1013 1014 //printf("+++ openDir(%s) in '%s'\n", dirName, (final char*) mAssetBase); 1015 1016 pDir = new AssetDir(); 1017 1018 /* 1019 * Scan the various directories, merging what we find into a single 1020 * vector. We want to scan them in reverse priority order so that 1021 * the ".EXCLUDE" processing works correctly. Also, if we decide we 1022 * want to remember where the file is coming from, we'll get the right 1023 * version. 1024 * 1025 * We start with Zip archives, then do loose files. 1026 */ 1027 pMergedInfo = new Ref<>(new SortedVector<AssetDir.FileInfo>()); 1028 1029 int i = mAssetPaths.size(); 1030 while (i > 0) { 1031 i--; 1032 final asset_path ap = mAssetPaths.get(i); 1033 if (ap.type == FileType.kFileTypeRegular) { 1034 ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); 1035 scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName); 1036 } else { 1037 ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); 1038 scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName); 1039 } 1040 } 1041 1042 // #if 0 1043 // printf("FILE LIST:\n"); 1044 // for (i = 0; i < (int) pMergedInfo.size(); i++) { 1045 // printf(" %d: (%d) '%s'\n", i, 1046 // pMergedInfo.itemAt(i).getFileType(), 1047 // ( final char*)pMergedInfo.itemAt(i).getFileName()); 1048 // } 1049 // #endif 1050 1051 pDir.setFileList(pMergedInfo.get()); 1052 return pDir; 1053 } 1054 } 1055 1056 // 1057 // /* 1058 // * Open a directory in the non-asset namespace. 1059 // * 1060 // * An "asset directory" is simply the combination of all asset paths' "assets/" directories. 1061 // * 1062 // * Pass in "" for the root dir. 1063 // */ 1064 // AssetDir* openNonAssetDir(final int cookie, final char* dirName) 1065 // { 1066 // AutoMutex _l(mLock); 1067 // 1068 // AssetDir* pDir = null; 1069 // SortedVector<AssetDir.FileInfo>* pMergedInfo = null; 1070 // 1071 // LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 1072 // assert(dirName != null); 1073 // 1074 // //printf("+++ openDir(%s) in '%s'\n", dirName, (final char*) mAssetBase); 1075 // 1076 // pDir = new AssetDir; 1077 // 1078 // pMergedInfo = new SortedVector<AssetDir.FileInfo>; 1079 // 1080 // final int which = static_cast<int>(cookie) - 1; 1081 // 1082 // if (which < mAssetPaths.size()) { 1083 // final asset_path& ap = mAssetPaths.itemAt(which); 1084 // if (ap.type == kFileTypeRegular) { 1085 // ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); 1086 // scanAndMergeZipLocked(pMergedInfo, ap, null, dirName); 1087 // } else { 1088 // ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); 1089 // scanAndMergeDirLocked(pMergedInfo, ap, null, dirName); 1090 // } 1091 // } 1092 // 1093 // #if 0 1094 // printf("FILE LIST:\n"); 1095 // for (i = 0; i < (int) pMergedInfo.size(); i++) { 1096 // printf(" %d: (%d) '%s'\n", i, 1097 // pMergedInfo.itemAt(i).getFileType(), 1098 // (final char*) pMergedInfo.itemAt(i).getFileName()); 1099 // } 1100 // #endif 1101 // 1102 // pDir.setFileList(pMergedInfo); 1103 // return pDir; 1104 // } 1105 // 1106 /* 1107 * Scan the contents of the specified directory and merge them into the 1108 * "pMergedInfo" vector, removing previous entries if we find "exclude" 1109 * directives. 1110 * 1111 * Returns "false" if we found nothing to contribute. 1112 */ scanAndMergeDirLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfoRef, final asset_path ap, final String rootDir, final String dirName)1113 boolean scanAndMergeDirLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfoRef, 1114 final asset_path ap, final String rootDir, final String dirName) { 1115 SortedVector<AssetDir.FileInfo> pMergedInfo = pMergedInfoRef.get(); 1116 assert (pMergedInfo != null); 1117 1118 //printf("scanAndMergeDir: %s %s %s\n", ap.path.string(), rootDir, dirName); 1119 1120 String8 path = createPathNameLocked(ap, rootDir); 1121 if (dirName.charAt(0) != '\0') { 1122 path.appendPath(dirName); 1123 } 1124 1125 SortedVector<AssetDir.FileInfo> pContents = scanDirLocked(path); 1126 if (pContents == null) { 1127 return false; 1128 } 1129 1130 // if we wanted to do an incremental cache fill, we would do it here 1131 1132 /* 1133 * Process "exclude" directives. If we find a filename that ends with 1134 * ".EXCLUDE", we look for a matching entry in the "merged" set, and 1135 * remove it if we find it. We also delete the "exclude" entry. 1136 */ 1137 int i, count, exclExtLen; 1138 1139 count = pContents.size(); 1140 exclExtLen = kExcludeExtension.length(); 1141 for (i = 0; i < count; i++) { 1142 final String name; 1143 int nameLen; 1144 1145 name = pContents.itemAt(i).getFileName().string(); 1146 nameLen = name.length(); 1147 if (name.endsWith(kExcludeExtension)) { 1148 String8 match = new String8(name, nameLen - exclExtLen); 1149 int matchIdx; 1150 1151 matchIdx = AssetDir.FileInfo.findEntry(pMergedInfo, match); 1152 if (matchIdx > 0) { 1153 ALOGV("Excluding '%s' [%s]\n", 1154 pMergedInfo.itemAt(matchIdx).getFileName().string(), 1155 pMergedInfo.itemAt(matchIdx).getSourceName().string()); 1156 pMergedInfo.removeAt(matchIdx); 1157 } else { 1158 //printf("+++ no match on '%s'\n", (final char*) match); 1159 } 1160 1161 ALOGD("HEY: size=%d removing %d\n", (int) pContents.size(), i); 1162 pContents.removeAt(i); 1163 i--; // adjust "for" loop 1164 count--; // and loop limit 1165 } 1166 } 1167 1168 mergeInfoLocked(pMergedInfoRef, pContents); 1169 1170 return true; 1171 } 1172 1173 /* 1174 * Scan the contents of the specified directory, and stuff what we find 1175 * into a newly-allocated vector. 1176 * 1177 * Files ending in ".gz" will have their extensions removed. 1178 * 1179 * We should probably think about skipping files with "illegal" names, 1180 * e.g. illegal characters (/\:) or excessive length. 1181 * 1182 * Returns null if the specified directory doesn't exist. 1183 */ scanDirLocked(final String8 path)1184 SortedVector<AssetDir.FileInfo> scanDirLocked(final String8 path) { 1185 1186 String8 pathCopy = new String8(path); 1187 SortedVector<AssetDir.FileInfo> pContents; 1188 //DIR* dir; 1189 File dir; 1190 FileType fileType; 1191 1192 ALOGV("Scanning dir '%s'\n", path.string()); 1193 1194 dir = new File(path.string()); 1195 if (!dir.exists()) { 1196 return null; 1197 } 1198 1199 pContents = new SortedVector<>(); 1200 1201 for (File entry : dir.listFiles()) { 1202 if (entry == null) { 1203 break; 1204 } 1205 1206 // if (strcmp(entry.d_name, ".") == 0 || 1207 // strcmp(entry.d_name, "..") == 0) 1208 // continue; 1209 1210 // #ifdef _DIRENT_HAVE_D_TYPE 1211 // if (entry.d_type == DT_REG) 1212 // fileType = kFileTypeRegular; 1213 // else if (entry.d_type == DT_DIR) 1214 // fileType = kFileTypeDirectory; 1215 // else 1216 // fileType = kFileTypeUnknown; 1217 // #else 1218 // stat the file 1219 fileType = getFileType(pathCopy.appendPath(entry.getName()).string()); 1220 // #endif 1221 1222 if (fileType != FileType.kFileTypeRegular && fileType != kFileTypeDirectory) { 1223 continue; 1224 } 1225 1226 AssetDir.FileInfo info = new AssetDir.FileInfo(); 1227 info.set(new String8(entry.getName()), fileType); 1228 if (info.getFileName().getPathExtension().equalsIgnoreCase(".gz")) { 1229 info.setFileName(info.getFileName().getBasePath()); 1230 } 1231 info.setSourceName(pathCopy.appendPath(info.getFileName().string())); 1232 pContents.add(info); 1233 } 1234 1235 return pContents; 1236 } 1237 1238 /* 1239 * Scan the contents out of the specified Zip archive, and merge what we 1240 * find into "pMergedInfo". If the Zip archive in question doesn't exist, 1241 * we return immediately. 1242 * 1243 * Returns "false" if we found nothing to contribute. 1244 */ scanAndMergeZipLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfo, final asset_path ap, final String rootDir, final String baseDirName)1245 boolean scanAndMergeZipLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfo, 1246 final asset_path ap, final String rootDir, final String baseDirName) { 1247 ZipFileRO pZip; 1248 List<String8> dirs = new ArrayList<>(); 1249 //AssetDir.FileInfo info = new FileInfo(); 1250 SortedVector<AssetDir.FileInfo> contents = new SortedVector<>(); 1251 String8 zipName; 1252 String8 dirName = new String8(); 1253 1254 pZip = mZipSet.getZip(ap.path.string()); 1255 if (pZip == null) { 1256 ALOGW("Failure opening zip %s\n", ap.path.string()); 1257 return false; 1258 } 1259 1260 zipName = ZipSet.getPathName(ap.path.string()); 1261 1262 /* convert "sounds" to "rootDir/sounds" */ 1263 if (rootDir != null) { 1264 dirName = new String8(rootDir); 1265 } 1266 1267 dirName.appendPath(baseDirName); 1268 1269 /* 1270 * Scan through the list of files, looking for a match. The files in 1271 * the Zip table of contents are not in sorted order, so we have to 1272 * process the entire list. We're looking for a string that begins 1273 * with the characters in "dirName", is followed by a '/', and has no 1274 * subsequent '/' in the stuff that follows. 1275 * 1276 * What makes this especially fun is that directories are not stored 1277 * explicitly in Zip archives, so we have to infer them from context. 1278 * When we see "sounds/foo.wav" we have to leave a note to ourselves 1279 * to insert a directory called "sounds" into the list. We store 1280 * these in temporary vector so that we only return each one once. 1281 * 1282 * Name comparisons are case-sensitive to match UNIX filesystem 1283 * semantics. 1284 */ 1285 int dirNameLen = dirName.length(); 1286 final Ref<Enumeration<? extends ZipEntry>> iterationCookie = new Ref<>(null); 1287 if (!pZip.startIteration(iterationCookie, dirName.string(), null)) { 1288 ALOGW("ZipFileRO.startIteration returned false"); 1289 return false; 1290 } 1291 1292 ZipEntryRO entry; 1293 while ((entry = pZip.nextEntry(iterationCookie.get())) != null) { 1294 1295 final Ref<String> nameBuf = new Ref<>(null); 1296 1297 if (pZip.getEntryFileName(entry, nameBuf) != 0) { 1298 // TODO: fix this if we expect to have long names 1299 ALOGE("ARGH: name too long?\n"); 1300 continue; 1301 } 1302 1303 // System.out.printf("Comparing %s in %s?\n", nameBuf.get(), dirName.string()); 1304 if (!nameBuf.get().startsWith(dirName.string() + '/')) { 1305 // not matching 1306 continue; 1307 } 1308 if (dirNameLen == 0 || nameBuf.get().charAt(dirNameLen) == '/') { 1309 int cp = 0; 1310 int nextSlashIndex; 1311 1312 //cp = nameBuf + dirNameLen; 1313 cp += dirNameLen; 1314 if (dirNameLen != 0) { 1315 cp++; // advance past the '/' 1316 } 1317 1318 nextSlashIndex = nameBuf.get().indexOf('/', cp); 1319 //xxx this may break if there are bare directory entries 1320 if (nextSlashIndex == -1) { 1321 /* this is a file in the requested directory */ 1322 String8 fileName = new String8(nameBuf.get()).getPathLeaf(); 1323 if (fileName.string().isEmpty()) { 1324 // ignore 1325 continue; 1326 } 1327 AssetDir.FileInfo info = new FileInfo(); 1328 info.set(fileName, FileType.kFileTypeRegular); 1329 1330 info.setSourceName( 1331 createZipSourceNameLocked(zipName, dirName, info.getFileName())); 1332 1333 contents.add(info); 1334 //printf("FOUND: file '%s'\n", info.getFileName().string()); 1335 } else { 1336 /* this is a subdir; add it if we don't already have it*/ 1337 String8 subdirName = new String8(nameBuf.get().substring(cp, nextSlashIndex)); 1338 int j; 1339 int N = dirs.size(); 1340 1341 for (j = 0; j < N; j++) { 1342 if (subdirName.equals(dirs.get(j))) { 1343 break; 1344 } 1345 } 1346 if (j == N) { 1347 dirs.add(subdirName); 1348 } 1349 1350 //printf("FOUND: dir '%s'\n", subdirName.string()); 1351 } 1352 } 1353 } 1354 1355 pZip.endIteration(iterationCookie); 1356 1357 /* 1358 * Add the set of unique directories. 1359 */ 1360 for (int i = 0; i < dirs.size(); i++) { 1361 AssetDir.FileInfo info = new FileInfo(); 1362 info.set(dirs.get(i), kFileTypeDirectory); 1363 info.setSourceName( 1364 createZipSourceNameLocked(zipName, dirName, info.getFileName())); 1365 contents.add(info); 1366 } 1367 1368 mergeInfoLocked(pMergedInfo, contents); 1369 1370 return true; 1371 1372 } 1373 1374 1375 /* 1376 * Merge two vectors of FileInfo. 1377 * 1378 * The merged contents will be stuffed into *pMergedInfo. 1379 * 1380 * If an entry for a file exists in both "pMergedInfo" and "pContents", 1381 * we use the newer "pContents" entry. 1382 */ mergeInfoLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfoRef, final SortedVector<AssetDir.FileInfo> pContents)1383 void mergeInfoLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfoRef, 1384 final SortedVector<AssetDir.FileInfo> pContents) { 1385 /* 1386 * Merge what we found in this directory with what we found in 1387 * other places. 1388 * 1389 * Two basic approaches: 1390 * (1) Create a new array that holds the unique values of the two 1391 * arrays. 1392 * (2) Take the elements from pContents and shove them into pMergedInfo. 1393 * 1394 * Because these are vectors of complex objects, moving elements around 1395 * inside the vector requires finalructing new objects and allocating 1396 * storage for members. With approach #1, we're always adding to the 1397 * end, whereas with #2 we could be inserting multiple elements at the 1398 * front of the vector. Approach #1 requires a full copy of the 1399 * contents of pMergedInfo, but approach #2 requires the same copy for 1400 * every insertion at the front of pMergedInfo. 1401 * 1402 * (We should probably use a SortedVector interface that allows us to 1403 * just stuff items in, trusting us to maintain the sort order.) 1404 */ 1405 SortedVector<AssetDir.FileInfo> pNewSorted; 1406 int mergeMax, contMax; 1407 int mergeIdx, contIdx; 1408 1409 SortedVector<AssetDir.FileInfo> pMergedInfo = pMergedInfoRef.get(); 1410 pNewSorted = new SortedVector<>(); 1411 mergeMax = pMergedInfo.size(); 1412 contMax = pContents.size(); 1413 mergeIdx = contIdx = 0; 1414 1415 while (mergeIdx < mergeMax || contIdx < contMax) { 1416 if (mergeIdx == mergeMax) { 1417 /* hit end of "merge" list, copy rest of "contents" */ 1418 pNewSorted.add(pContents.itemAt(contIdx)); 1419 contIdx++; 1420 } else if (contIdx == contMax) { 1421 /* hit end of "cont" list, copy rest of "merge" */ 1422 pNewSorted.add(pMergedInfo.itemAt(mergeIdx)); 1423 mergeIdx++; 1424 } else if (pMergedInfo.itemAt(mergeIdx) == pContents.itemAt(contIdx)) { 1425 /* items are identical, add newer and advance both indices */ 1426 pNewSorted.add(pContents.itemAt(contIdx)); 1427 mergeIdx++; 1428 contIdx++; 1429 } else if (pMergedInfo.itemAt(mergeIdx).isLessThan(pContents.itemAt(contIdx))) { 1430 /* "merge" is lower, add that one */ 1431 pNewSorted.add(pMergedInfo.itemAt(mergeIdx)); 1432 mergeIdx++; 1433 } else { 1434 /* "cont" is lower, add that one */ 1435 assert pContents.itemAt(contIdx).isLessThan(pMergedInfo.itemAt(mergeIdx)); 1436 pNewSorted.add(pContents.itemAt(contIdx)); 1437 contIdx++; 1438 } 1439 } 1440 1441 /* 1442 * Overwrite the "merged" list with the new stuff. 1443 */ 1444 pMergedInfoRef.set(pNewSorted); 1445 1446 // #if 0 // for Vector, rather than SortedVector 1447 // int i, j; 1448 // for (i = pContents.size() -1; i >= 0; i--) { 1449 // boolean add = true; 1450 // 1451 // for (j = pMergedInfo.size() -1; j >= 0; j--) { 1452 // /* case-sensitive comparisons, to behave like UNIX fs */ 1453 // if (strcmp(pContents.itemAt(i).mFileName, 1454 // pMergedInfo.itemAt(j).mFileName) == 0) 1455 // { 1456 // /* match, don't add this entry */ 1457 // add = false; 1458 // break; 1459 // } 1460 // } 1461 // 1462 // if (add) 1463 // pMergedInfo.add(pContents.itemAt(i)); 1464 // } 1465 // #endif 1466 } 1467 1468 /* 1469 * =========================================================================== 1470 * SharedZip 1471 * =========================================================================== 1472 */ 1473 1474 static class SharedZip /*: public RefBase */ { 1475 1476 final String mPath; 1477 final ZipFileRO mZipFile; 1478 final long mModWhen; 1479 1480 Asset mResourceTableAsset; 1481 ResTable mResourceTable; 1482 1483 List<asset_path> mOverlays; 1484 1485 final static Object gLock = new Object(); 1486 final static Map<String8, WeakReference<SharedZip>> gOpen = new HashMap<>(); 1487 SharedZip(String path, long modWhen)1488 public SharedZip(String path, long modWhen) { 1489 this.mPath = path; 1490 this.mModWhen = modWhen; 1491 this.mResourceTableAsset = null; 1492 this.mResourceTable = null; 1493 1494 if (kIsDebug) { 1495 ALOGI("Creating SharedZip %s %s\n", this, mPath); 1496 } 1497 ALOGV("+++ opening zip '%s'\n", mPath); 1498 this.mZipFile = ZipFileRO.open(mPath); 1499 if (mZipFile == null) { 1500 ALOGD("failed to open Zip archive '%s'\n", mPath); 1501 } 1502 } 1503 get(final String8 path)1504 static SharedZip get(final String8 path) { 1505 return get(path, true); 1506 } 1507 get(final String8 path, boolean createIfNotPresent)1508 static SharedZip get(final String8 path, boolean createIfNotPresent) { 1509 synchronized (gLock) { 1510 long modWhen = getFileModDate(path.string()); 1511 WeakReference<SharedZip> ref = gOpen.get(path); 1512 SharedZip zip = ref == null ? null : ref.get(); 1513 if (zip != null && zip.mModWhen == modWhen) { 1514 return zip; 1515 } 1516 if (zip == null && !createIfNotPresent) { 1517 return null; 1518 } 1519 zip = new SharedZip(path.string(), modWhen); 1520 gOpen.put(path, new WeakReference<>(zip)); 1521 return zip; 1522 1523 } 1524 1525 } 1526 getZip()1527 ZipFileRO getZip() { 1528 return mZipFile; 1529 } 1530 getResourceTableAsset()1531 Asset getResourceTableAsset() { 1532 synchronized (gLock) { 1533 ALOGV("Getting from SharedZip %s resource asset %s\n", this, mResourceTableAsset); 1534 return mResourceTableAsset; 1535 } 1536 } 1537 setResourceTableAsset(Asset asset)1538 Asset setResourceTableAsset(Asset asset) { 1539 synchronized (gLock) { 1540 if (mResourceTableAsset == null) { 1541 // This is not thread safe the first time it is called, so 1542 // do it here with the global lock held. 1543 asset.getBuffer(true); 1544 mResourceTableAsset = asset; 1545 return asset; 1546 } 1547 } 1548 return mResourceTableAsset; 1549 } 1550 getResourceTable()1551 ResTable getResourceTable() { 1552 ALOGV("Getting from SharedZip %s resource table %s\n", this, mResourceTable); 1553 return mResourceTable; 1554 } 1555 setResourceTable(ResTable res)1556 ResTable setResourceTable(ResTable res) { 1557 synchronized (gLock) { 1558 if (mResourceTable == null) { 1559 mResourceTable = res; 1560 return res; 1561 } 1562 } 1563 return mResourceTable; 1564 } 1565 1566 // boolean SharedZip.isUpToDate() 1567 // { 1568 // time_t modWhen = getFileModDate(mPath.string()); 1569 // return mModWhen == modWhen; 1570 // } 1571 // 1572 // void SharedZip.addOverlay(final asset_path& ap) 1573 // { 1574 // mOverlays.add(ap); 1575 // } 1576 // 1577 // boolean SharedZip.getOverlay(int idx, asset_path* out) final 1578 // { 1579 // if (idx >= mOverlays.size()) { 1580 // return false; 1581 // } 1582 // *out = mOverlays[idx]; 1583 // return true; 1584 // } 1585 // 1586 // SharedZip.~SharedZip() 1587 // { 1588 // if (kIsDebug) { 1589 // ALOGI("Destroying SharedZip %s %s\n", this, (final char*)mPath); 1590 // } 1591 // if (mResourceTable != null) { 1592 // delete mResourceTable; 1593 // } 1594 // if (mResourceTableAsset != null) { 1595 // delete mResourceTableAsset; 1596 // } 1597 // if (mZipFile != null) { 1598 // delete mZipFile; 1599 // ALOGV("Closed '%s'\n", mPath.string()); 1600 // } 1601 // } 1602 1603 @Override toString()1604 public String toString() { 1605 String id = Integer.toString(System.identityHashCode(this), 16); 1606 return "SharedZip{mPath='" + mPath + "\', id=0x" + id + "}"; 1607 } 1608 } 1609 1610 1611 /* 1612 * Manage a set of Zip files. For each file we need a pointer to the 1613 * ZipFile and a time_t with the file's modification date. 1614 * 1615 * We currently only have two zip files (current app, "common" app). 1616 * (This was originally written for 8, based on app/locale/vendor.) 1617 */ 1618 static class ZipSet { 1619 1620 final List<String> mZipPath = new ArrayList<>(); 1621 final List<SharedZip> mZipFile = new ArrayList<>(); 1622 1623 /* 1624 * =========================================================================== 1625 * ZipSet 1626 * =========================================================================== 1627 */ 1628 1629 /* 1630 * Destructor. Close any open archives. 1631 */ 1632 // ZipSet.~ZipSet(void) 1633 @Override finalize()1634 protected void finalize() { 1635 int N = mZipFile.size(); 1636 for (int i = 0; i < N; i++) { 1637 closeZip(i); 1638 } 1639 } 1640 1641 /* 1642 * Close a Zip file and reset the entry. 1643 */ closeZip(int idx)1644 void closeZip(int idx) { 1645 mZipFile.set(idx, null); 1646 } 1647 1648 1649 /* 1650 * Retrieve the appropriate Zip file from the set. 1651 */ getZip(final String path)1652 synchronized ZipFileRO getZip(final String path) { 1653 int idx = getIndex(path); 1654 SharedZip zip = mZipFile.get(idx); 1655 if (zip == null) { 1656 zip = SharedZip.get(new String8(path)); 1657 mZipFile.set(idx, zip); 1658 } 1659 return zip.getZip(); 1660 } 1661 getZipResourceTableAsset(final String8 path)1662 synchronized Asset getZipResourceTableAsset(final String8 path) { 1663 int idx = getIndex(path.string()); 1664 SharedZip zip = mZipFile.get(idx); 1665 if (zip == null) { 1666 zip = SharedZip.get(path); 1667 mZipFile.set(idx, zip); 1668 } 1669 return zip.getResourceTableAsset(); 1670 } 1671 setZipResourceTableAsset(final String8 path, Asset asset)1672 synchronized Asset setZipResourceTableAsset(final String8 path, Asset asset) { 1673 int idx = getIndex(path.string()); 1674 SharedZip zip = mZipFile.get(idx); 1675 // doesn't make sense to call before previously accessing. 1676 return zip.setResourceTableAsset(asset); 1677 } 1678 getZipResourceTable(final String8 path)1679 synchronized ResTable getZipResourceTable(final String8 path) { 1680 int idx = getIndex(path.string()); 1681 SharedZip zip = mZipFile.get(idx); 1682 if (zip == null) { 1683 zip = SharedZip.get(path); 1684 mZipFile.set(idx, zip); 1685 } 1686 return zip.getResourceTable(); 1687 } 1688 setZipResourceTable(final String8 path, ResTable res)1689 synchronized ResTable setZipResourceTable(final String8 path, ResTable res) { 1690 int idx = getIndex(path.string()); 1691 SharedZip zip = mZipFile.get(idx); 1692 // doesn't make sense to call before previously accessing. 1693 return zip.setResourceTable(res); 1694 } 1695 1696 /* 1697 * Generate the partial pathname for the specified archive. The caller 1698 * gets to prepend the asset root directory. 1699 * 1700 * Returns something like "common/en-US-noogle.jar". 1701 */ getPathName(final String zipPath)1702 static String8 getPathName(final String zipPath) { 1703 return new String8(zipPath); 1704 } 1705 1706 // 1707 // boolean ZipSet.isUpToDate() 1708 // { 1709 // final int N = mZipFile.size(); 1710 // for (int i=0; i<N; i++) { 1711 // if (mZipFile[i] != null && !mZipFile[i].isUpToDate()) { 1712 // return false; 1713 // } 1714 // } 1715 // return true; 1716 // } 1717 // 1718 // void ZipSet.addOverlay(final String8& path, final asset_path& overlay) 1719 // { 1720 // int idx = getIndex(path); 1721 // sp<SharedZip> zip = mZipFile[idx]; 1722 // zip.addOverlay(overlay); 1723 // } 1724 // 1725 // boolean ZipSet.getOverlay(final String8& path, int idx, asset_path* out) final 1726 // { 1727 // sp<SharedZip> zip = SharedZip.get(path, false); 1728 // if (zip == null) { 1729 // return false; 1730 // } 1731 // return zip.getOverlay(idx, out); 1732 // } 1733 // 1734 /* 1735 * Compute the zip file's index. 1736 * 1737 * "appName", "locale", and "vendor" should be set to null to indicate the 1738 * default directory. 1739 */ getIndex(final String zip)1740 int getIndex(final String zip) { 1741 final int N = mZipPath.size(); 1742 for (int i = 0; i < N; i++) { 1743 if (Objects.equals(mZipPath.get(i), zip)) { 1744 return i; 1745 } 1746 } 1747 1748 mZipPath.add(zip); 1749 mZipFile.add(null); 1750 1751 return mZipPath.size() - 1; 1752 } 1753 1754 } 1755 getFileModDate(String path)1756 private static long getFileModDate(String path) { 1757 try { 1758 return Files.getLastModifiedTime(Paths.get(path)).toMillis(); 1759 } catch (IOException e) { 1760 throw new RuntimeException(e); 1761 } 1762 } 1763 getAssetPaths()1764 public List<AssetPath> getAssetPaths() { 1765 synchronized (mLock) { 1766 ArrayList<AssetPath> assetPaths = new ArrayList<>(mAssetPaths.size()); 1767 for (asset_path assetPath : mAssetPaths) { 1768 Path path; 1769 switch (assetPath.type) { 1770 case kFileTypeDirectory: 1771 path = Fs.fromUrl(assetPath.path.string()); 1772 break; 1773 case kFileTypeRegular: 1774 path = Fs.fromUrl(assetPath.path.string()); 1775 break; 1776 default: 1777 throw new IllegalStateException( 1778 "Unsupported type " + assetPath.type + " for + " + assetPath.path.string()); 1779 } 1780 assetPaths.add(new AssetPath(path, assetPath.isSystemAsset)); 1781 } 1782 return assetPaths; 1783 } 1784 } 1785 } 1786