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