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