Home | History | Annotate | Download | only in android
      1 package org.robolectric.res.android;
      2 
      3 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/ApkAssets.h
      4 // and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ApkAssets.cpp
      5 
      6 import static org.robolectric.res.android.CppAssetManager.FileType.kFileTypeDirectory;
      7 import static org.robolectric.res.android.CppAssetManager.FileType.kFileTypeRegular;
      8 import static org.robolectric.res.android.Util.CHECK;
      9 import static org.robolectric.res.android.ZipFileRO.OpenArchive;
     10 import static org.robolectric.res.android.ZipFileRO.kCompressDeflated;
     11 
     12 import java.nio.ByteBuffer;
     13 import java.nio.ByteOrder;
     14 import java.util.Enumeration;
     15 import java.util.HashSet;
     16 import java.util.Set;
     17 import java.util.zip.ZipEntry;
     18 import org.robolectric.res.android.Asset.AccessMode;
     19 import org.robolectric.res.android.CppAssetManager.FileType;
     20 import org.robolectric.res.android.Idmap.LoadedIdmap;
     21 import org.robolectric.res.android.ZipFileRO.ZipEntryRO;
     22 import org.robolectric.util.PerfStatsCollector;
     23 
     24 //
     25 // #ifndef APKASSETS_H_
     26 // #define APKASSETS_H_
     27 //
     28 // #include <memory>
     29 // #include <string>
     30 //
     31 // #include "android-base/macros.h"
     32 // #include "ziparchive/zip_archive.h"
     33 //
     34 // #include "androidfw/Asset.h"
     35 // #include "androidfw/LoadedArsc.h"
     36 // #include "androidfw/misc.h"
     37 //
     38 // namespace android {
     39 //
     40 // // Holds an APK.
     41 @SuppressWarnings("NewApi")
     42 public class CppApkAssets {
     43   private static final String kResourcesArsc = "resources.arsc";
     44 //  public:
     45 //   static std::unique_ptr<const ApkAssets> Load(const String& path, bool system = false);
     46 //   static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const String& path,
     47 //                                                               bool system = false);
     48 //
     49 //   std::unique_ptr<Asset> Open(const String& path,
     50 //                               Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
     51 //
     52 //   bool ForEachFile(const String& path,
     53 //                    const std::function<void(const StringPiece&, FileType)>& f) const;
     54 
     55 
     56   public CppApkAssets(ZipArchiveHandle zip_handle_, String path_) {
     57     this.zip_handle_ = zip_handle_;
     58     this.path_ = path_;
     59   }
     60 
     61   public String GetPath() { return path_; }
     62 
     63   // This is never nullptr.
     64   public LoadedArsc GetLoadedArsc() {
     65     return loaded_arsc_;
     66   }
     67 
     68   //  private:
     69 //   DISALLOW_COPY_AND_ASSIGN(ApkAssets);
     70 //
     71 //   static std::unique_ptr<const ApkAssets> LoadImpl(const String& path, bool system,
     72 //                                                    bool load_as_shared_library);
     73 //
     74 //   ApkAssets() = default;
     75 //
     76 //   struct ZipArchivePtrCloser {
     77 //     void operator()(::ZipArchiveHandle handle) { ::CloseArchive(handle); }
     78 //   };
     79 //
     80 //   using ZipArchivePtr =
     81 //       std::unique_ptr<typename std::remove_pointer<::ZipArchiveHandle>::type, ZipArchivePtrCloser>;
     82 
     83   ZipArchiveHandle zip_handle_;
     84   private String path_;
     85   Asset resources_asset_;
     86   Asset idmap_asset_;
     87   private LoadedArsc loaded_arsc_;
     88   // };
     89 //
     90 // }  // namespace android
     91 //
     92 // #endif /* APKASSETS_H_ */
     93 //
     94 // #define ATRACE_TAG ATRACE_TAG_RESOURCES
     95 //
     96 // #include "androidfw/ApkAssets.h"
     97 //
     98 // #include <algorithm>
     99 //
    100 // #include "android-base/logging.h"
    101 // #include "utils/FileMap.h"
    102 // #include "utils/Trace.h"
    103 // #include "ziparchive/zip_archive.h"
    104 //
    105 // #include "androidfw/Asset.h"
    106 // #include "androidfw/Util.h"
    107 //
    108 // namespace android {
    109 //
    110 // Creates an ApkAssets.
    111 // If `system` is true, the package is marked as a system package, and allows some functions to
    112 // filter out this package when computing what configurations/resources are available.
    113 // std::unique_ptr<const ApkAssets> ApkAssets::Load(const String& path, bool system) {
    114   public static CppApkAssets Load(String path, boolean system) {
    115     return LoadImpl(/*{}*/-1 /*fd*/, path, null, null, system, false /*load_as_shared_library*/);
    116   }
    117 
    118   // Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
    119   // If `system` is true, the package is marked as a system package, and allows some functions to
    120   // filter out this package when computing what configurations/resources are available.
    121 // std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const String& path,
    122 //                                                                 bool system) {
    123   public static CppApkAssets LoadAsSharedLibrary(String path,
    124       boolean system) {
    125     return LoadImpl(/*{}*/ -1 /*fd*/, path, null, null, system, true /*load_as_shared_library*/);
    126   }
    127 
    128   // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
    129   // data.
    130   // If `system` is true, the package is marked as a system package, and allows some functions to
    131   // filter out this package when computing what configurations/resources are available.
    132 // std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
    133 //                                                         bool system) {
    134   public static CppApkAssets LoadOverlay(String idmap_path,
    135       boolean system) {
    136     throw new UnsupportedOperationException();
    137     // Asset idmap_asset = CreateAssetFromFile(idmap_path);
    138     // if (idmap_asset == null) {
    139     //   return {};
    140     // }
    141     //
    142     // StringPiece idmap_data(
    143     //     reinterpret_cast<char*>(idmap_asset.getBuffer(true /*wordAligned*/)),
    144     //     static_cast<size_t>(idmap_asset.getLength()));
    145     // LoadedIdmap loaded_idmap = LoadedIdmap.Load(idmap_data);
    146     // if (loaded_idmap == null) {
    147     //   System.err.println( + "failed to load IDMAP " + idmap_path;
    148     //   return {};
    149     // }
    150     // return LoadImpl({} /*fd*/, loaded_idmap.OverlayApkPath(), std.move(idmap_asset),
    151     //     std.move(loaded_idmap), system, false /*load_as_shared_library*/);
    152   }
    153 
    154   // Creates an ApkAssets from the given file descriptor, and takes ownership of the file
    155   // descriptor. The `friendly_name` is some name that will be used to identify the source of
    156   // this ApkAssets in log messages and other debug scenarios.
    157   // If `system` is true, the package is marked as a system package, and allows some functions to
    158   // filter out this package when computing what configurations/resources are available.
    159   // If `force_shared_lib` is true, any package with ID 0x7f is loaded as a shared library.
    160 // std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd,
    161 //                                                        const std::string& friendly_name,
    162 //                                                        bool system, bool force_shared_lib) {
    163 //   public static ApkAssets LoadFromFd(unique_fd fd,
    164 //       String friendly_name,
    165 //       boolean system, boolean force_shared_lib) {
    166 //     return LoadImpl(std.move(fd), friendly_name, null /*idmap_asset*/, null /*loaded_idmap*/,
    167 //         system, force_shared_lib);
    168 //   }
    169 
    170   // std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
    171   static Asset CreateAssetFromFile(String path) {
    172     throw new UnsupportedOperationException();
    173     // unique_fd fd(base.utf8.open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
    174     // if (fd == -1) {
    175     //   System.err.println( + "Failed to open file '" + path + "': " + SystemErrorCodeToString(errno);
    176     //   return {};
    177     // }
    178     //
    179     // long file_len = lseek64(fd, 0, SEEK_END);
    180     // if (file_len < 0) {
    181     //   System.err.println( + "Failed to get size of file '" + path + "': " + SystemErrorCodeToString(errno);
    182     //   return {};
    183     // }
    184     //
    185     // std.unique_ptr<FileMap> file_map = util.make_unique<FileMap>();
    186     // if (!file_map.create(path.c_str(), fd, 0, static_cast<size_t>(file_len), true /*readOnly*/)) {
    187     //   System.err.println( + "Failed to mmap file '" + path + "': " + SystemErrorCodeToString(errno);
    188     //   return {};
    189     // }
    190     // return Asset.createFromUncompressedMap(std.move(file_map), Asset.AccessMode.ACCESS_RANDOM);
    191   }
    192 
    193   /**
    194    * Measure performance implications of loading {@link CppApkAssets}.
    195    */
    196   static CppApkAssets LoadImpl(
    197       int fd, String path, Asset idmap_asset,
    198       LoadedIdmap loaded_idmap, boolean system, boolean load_as_shared_library) {
    199     return PerfStatsCollector.getInstance()
    200         .measure(
    201             "load binary " + (system ? "framework" : "app") + " resources",
    202             () ->
    203                 LoadImpl_measured(
    204                     fd, path, idmap_asset, loaded_idmap, system, load_as_shared_library));
    205   }
    206 
    207   // std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
    208   //     unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
    209   //     std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
    210   static CppApkAssets LoadImpl_measured(
    211       int fd, String path, Asset idmap_asset,
    212       LoadedIdmap loaded_idmap, boolean system, boolean load_as_shared_library) {
    213     Ref<ZipArchiveHandle> unmanaged_handle = new Ref<>(null);
    214     int result;
    215     if (fd >= 0) {
    216       throw new UnsupportedOperationException();
    217       // result =
    218       //   OpenArchiveFd(fd.release(), path, &unmanaged_handle, true /*assume_ownership*/);
    219     } else {
    220       result = OpenArchive(path, unmanaged_handle);
    221     }
    222 
    223     if (result != 0) {
    224       System.err.println("Failed to open APK '" + path + "' " + ErrorCodeString(result));
    225       return null;
    226     }
    227 
    228     // Wrap the handle in a unique_ptr so it gets automatically closed.
    229     CppApkAssets loaded_apk = new CppApkAssets(unmanaged_handle.get(), path);
    230 
    231     // Find the resource table.
    232     String entry_name = kResourcesArsc;
    233     Ref<ZipEntry> entry = new Ref<>(null);
    234     // result = FindEntry(loaded_apk.zip_handle_.get(), entry_name, &entry);
    235     result = ZipFileRO.FindEntry(loaded_apk.zip_handle_, entry_name, entry);
    236     if (result != 0) {
    237       // There is no resources.arsc, so create an empty LoadedArsc and return.
    238       loaded_apk.loaded_arsc_ = LoadedArsc.CreateEmpty();
    239       return loaded_apk;
    240     }
    241 
    242     if (entry.get().getMethod() == kCompressDeflated) {
    243       System.out.println(kResourcesArsc + " in APK '" + path + "' is compressed.");
    244     }
    245 
    246     // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
    247     loaded_apk.resources_asset_ = loaded_apk.Open(kResourcesArsc, Asset.AccessMode.ACCESS_BUFFER);
    248     if (loaded_apk.resources_asset_ == null) {
    249       System.err.println("Failed to open '" + kResourcesArsc + "' in APK '" + path + "'.");
    250       return null;
    251     }
    252 
    253     // Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
    254     loaded_apk.idmap_asset_ = idmap_asset;
    255 
    256   // const StringPiece data(
    257   //       reinterpret_cast<const char*>(loaded_apk.resources_asset_.getBuffer(true /*wordAligned*/)),
    258   //       loaded_apk.resources_asset_.getLength());
    259     StringPiece data = new StringPiece(
    260         ByteBuffer.wrap(loaded_apk.resources_asset_.getBuffer(true /*wordAligned*/))
    261             .order(ByteOrder.LITTLE_ENDIAN),
    262         0 /*(int) loaded_apk.resources_asset_.getLength()*/);
    263     loaded_apk.loaded_arsc_ =
    264         LoadedArsc.Load(data, loaded_idmap, system, load_as_shared_library);
    265     if (loaded_apk.loaded_arsc_ == null) {
    266       System.err.println("Failed to load '" + kResourcesArsc + "' in APK '" + path + "'.");
    267       return null;
    268     }
    269 
    270     // Need to force a move for mingw32.
    271     return loaded_apk;
    272   }
    273 
    274   private static String ErrorCodeString(int result) {
    275     return "Error " + result;
    276   }
    277 
    278   public Asset Open(String path, AccessMode mode) {
    279     CHECK(zip_handle_ != null);
    280 
    281     String name = path;
    282     ZipFileRO zipFileRO = ZipFileRO.open(zip_handle_.zipFile.getName());
    283     ZipEntryRO entry;
    284     entry = zipFileRO.findEntryByName(name);
    285     // int result = FindEntry(zip_handle_.get(), name, &entry);
    286     // if (result != 0) {
    287     //   LOG(ERROR) + "No entry '" + path + "' found in APK '" + path_ + "'";
    288     //   return {};
    289     // }
    290     if (entry == null) {
    291       return null;
    292     }
    293 
    294     if (entry.entry.getMethod() == kCompressDeflated) {
    295       // FileMap map = new FileMap();
    296       // if (!map.create(path_, .GetFileDescriptor(zip_handle_), entry.offset,
    297       //     entry.getCompressedSize(), true /*readOnly*/)) {
    298       //   LOG(ERROR) + "Failed to mmap file '" + path + "' in APK '" + path_ + "'";
    299       //   return {};
    300       // }
    301       FileMap map = zipFileRO.createEntryFileMap(entry);
    302 
    303       Asset asset =
    304           Asset.createFromCompressedMap(map, (int) entry.entry.getSize(), mode);
    305       if (asset == null) {
    306         System.err.println("Failed to decompress '" + path + "'.");
    307         return null;
    308       }
    309       return asset;
    310     } else {
    311       FileMap map = zipFileRO.createEntryFileMap(entry);
    312 
    313       // if (!map.create(path_, .GetFileDescriptor(zip_handle_.get()), entry.offset,
    314       //     entry.uncompressed_length, true /*readOnly*/)) {
    315       //   System.err.println("Failed to mmap file '" + path + "' in APK '" + path_ + "'");
    316       //   return null;
    317       // }
    318 
    319       Asset asset = Asset.createFromUncompressedMap(map, mode);
    320       if (asset == null) {
    321         System.err.println("Failed to mmap file '" + path + "' in APK '" + path_ + "'");
    322         return null;
    323       }
    324       return asset;
    325     }
    326   }
    327 
    328   interface ForEachFileCallback {
    329     void callback(String string, FileType fileType);
    330   }
    331 
    332   boolean ForEachFile(String root_path,
    333       ForEachFileCallback f) {
    334     CHECK(zip_handle_ != null);
    335 
    336     String root_path_full = root_path;
    337     // if (root_path_full.back() != '/') {
    338     if (!root_path_full.endsWith("/")) {
    339       root_path_full += '/';
    340     }
    341 
    342     String prefix = root_path_full;
    343     Enumeration<? extends ZipEntry> entries = zip_handle_.zipFile.entries();
    344     // if (StartIteration(zip_handle_.get(), &cookie, &prefix, null) != 0) {
    345     //   return false;
    346     // }
    347     if (!entries.hasMoreElements()) {
    348       return false;
    349     }
    350 
    351     // String name;
    352     // ZipEntry entry;
    353 
    354     // We need to hold back directories because many paths will contain them and we want to only
    355     // surface one.
    356     final Set<String> dirs = new HashSet<>();
    357 
    358     // int32_t result;
    359     // while ((result = Next(cookie, &entry, &name)) == 0) {
    360     while (entries.hasMoreElements()) {
    361       ZipEntry zipEntry =  entries.nextElement();
    362       if (!zipEntry.getName().startsWith(prefix)) {
    363         continue;
    364       }
    365 
    366       // StringPiece full_file_path(reinterpret_cast<const char*>(name.name), name.name_length);
    367       String full_file_path = zipEntry.getName();
    368 
    369       // StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
    370       String leaf_file_path = full_file_path.substring(root_path_full.length());
    371 
    372       if (!leaf_file_path.isEmpty()) {
    373         // auto iter = stdfind(leaf_file_path.begin(), leaf_file_path.end(), '/');
    374 
    375         // if (iter != leaf_file_path.end()) {
    376         //   stdstring dir =
    377         //       leaf_file_path.substr(0, stddistance(leaf_file_path.begin(), iter)).to_string();
    378         //   dirs.insert(stdmove(dir));
    379         if (zipEntry.isDirectory()) {
    380           dirs.add(leaf_file_path.substring(0, leaf_file_path.indexOf("/")));
    381         } else {
    382           f.callback(leaf_file_path, kFileTypeRegular);
    383         }
    384       }
    385     }
    386     // EndIteration(cookie);
    387 
    388     // Now present the unique directories.
    389     for (final String dir : dirs) {
    390       f.callback(dir, kFileTypeDirectory);
    391     }
    392 
    393     // -1 is end of iteration, anything else is an error.
    394     // return result == -1;
    395     return true;
    396   }
    397 //
    398 }  // namespace android
    399 
    400