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