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