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