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