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