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