• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CppApkAssets(ZipArchiveHandle zip_handle_, String path_)56   public CppApkAssets(ZipArchiveHandle zip_handle_, String path_) {
57     this.zip_handle_ = zip_handle_;
58     this.path_ = path_;
59   }
60 
GetPath()61   public String GetPath() { return path_; }
62 
63   // This is never nullptr.
GetLoadedArsc()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) {
Load(String path, boolean 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) {
LoadAsSharedLibrary(String path, boolean 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) {
LoadOverlay(String idmap_path, boolean 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) {
CreateAssetFromFile(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    */
LoadImpl( int fd, String path, Asset idmap_asset, LoadedIdmap loaded_idmap, boolean system, boolean load_as_shared_library)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) {
LoadImpl_measured( int fd, String path, Asset idmap_asset, LoadedIdmap loaded_idmap, boolean system, boolean 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 
ErrorCodeString(int result)274   private static String ErrorCodeString(int result) {
275     return "Error " + result;
276   }
277 
Open(String path, AccessMode mode)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 {
callback(String string, FileType fileType)329     void callback(String string, FileType fileType);
330   }
331 
ForEachFile(String root_path, ForEachFileCallback f)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