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