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