AssetManager2: Various fixes

- Use FileMaps to open Assets (prevents closing of ApkAssets underlying
zip)
- Implement OpenDir and List methods
- Fix issue where DynamicRefTable wasn't properly constructed

Test: make libandroidfw_tests
Change-Id: Ib21a84e1114d028120744aa3bc1c6eb9d9399fa8
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index fe68ec0..a5b1d29 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -18,7 +18,10 @@
 
 #include "androidfw/ApkAssets.h"
 
+#include <algorithm>
+
 #include "android-base/logging.h"
+#include "utils/FileMap.h"
 #include "utils/Trace.h"
 #include "ziparchive/zip_archive.h"
 
@@ -62,13 +65,13 @@
     LOG(WARNING) << "resources.arsc is compressed.";
   }
 
+  loaded_apk->path_ = path;
   loaded_apk->resources_asset_ =
       loaded_apk->Open("resources.arsc", Asset::AccessMode::ACCESS_BUFFER);
   if (loaded_apk->resources_asset_ == nullptr) {
     return {};
   }
 
-  loaded_apk->path_ = path;
   loaded_apk->loaded_arsc_ =
       LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/),
                        loaded_apk->resources_asset_->getLength(), system, load_as_shared_library);
@@ -80,37 +83,93 @@
   return std::move(loaded_apk);
 }
 
-std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode /*mode*/) const {
-  ATRACE_NAME("ApkAssets::Open");
+std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
+  ATRACE_CALL();
   CHECK(zip_handle_ != nullptr);
 
   ::ZipString name(path.c_str());
   ::ZipEntry entry;
   int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
   if (result != 0) {
-    LOG(ERROR) << "No entry '" << path << "' found in APK.";
+    LOG(ERROR) << "No entry '" << path << "' found in APK '" << path_ << "'";
     return {};
   }
 
   if (entry.method == kCompressDeflated) {
-    auto compressed_asset = util::make_unique<_CompressedAsset>();
-    if (compressed_asset->openChunk(::GetFileDescriptor(zip_handle_.get()), entry.offset,
-                                    entry.method, entry.uncompressed_length,
-                                    entry.compressed_length) != NO_ERROR) {
+    std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
+    if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
+                     entry.compressed_length, true /*readOnly*/)) {
+      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
+      return {};
+    }
+
+    std::unique_ptr<Asset> asset =
+        Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
+    if (asset == nullptr) {
       LOG(ERROR) << "Failed to decompress '" << path << "'.";
       return {};
     }
-    return std::move(compressed_asset);
+    return asset;
   } else {
-    auto uncompressed_asset = util::make_unique<_FileAsset>();
-    if (uncompressed_asset->openChunk(path.c_str(), ::GetFileDescriptor(zip_handle_.get()),
-                                      entry.offset, entry.uncompressed_length) != NO_ERROR) {
-      LOG(ERROR) << "Failed to mmap '" << path << "'.";
+    std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
+    if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
+                     entry.uncompressed_length, true /*readOnly*/)) {
+      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
       return {};
     }
-    return std::move(uncompressed_asset);
+
+    std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), mode);
+    if (asset == nullptr) {
+      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
+      return {};
+    }
+    return asset;
   }
-  return {};
+}
+
+bool ApkAssets::ForEachFile(const std::string& root_path,
+                            const std::function<void(const StringPiece&, FileType)>& f) const {
+  CHECK(zip_handle_ != nullptr);
+
+  std::string root_path_full = root_path;
+  if (root_path_full.back() != '/') {
+    root_path_full += '/';
+  }
+
+  ::ZipString prefix(root_path_full.c_str());
+  void* cookie;
+  if (::StartIteration(zip_handle_.get(), &cookie, &prefix, nullptr) != 0) {
+    return false;
+  }
+
+  ::ZipString name;
+  ::ZipEntry entry;
+
+  // We need to hold back directories because many paths will contain them and we want to only
+  // surface one.
+  std::set<std::string> dirs;
+
+  int32_t result;
+  while ((result = ::Next(cookie, &entry, &name)) == 0) {
+    StringPiece full_file_path(reinterpret_cast<const char*>(name.name), name.name_length);
+    StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
+    auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
+    if (iter != leaf_file_path.end()) {
+      dirs.insert(
+          leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string());
+    } else if (!leaf_file_path.empty()) {
+      f(leaf_file_path, kFileTypeRegular);
+    }
+  }
+  ::EndIteration(cookie);
+
+  // Now present the unique directories.
+  for (const std::string& dir : dirs) {
+    f(dir, kFileTypeDirectory);
+  }
+
+  // -1 is end of iteration, anything else is an error.
+  return result == -1;
 }
 
 }  // namespace android
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 8e8c6a2..247458d 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -23,6 +23,7 @@
 
 #include <androidfw/Asset.h>
 #include <androidfw/StreamingZipInflater.h>
+#include <androidfw/Util.h>
 #include <androidfw/ZipFileRO.h>
 #include <androidfw/ZipUtils.h>
 #include <utils/Atomic.h>
@@ -298,6 +299,22 @@
     return pAsset;
 }
 
+/*static*/ std::unique_ptr<Asset> Asset::createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,
+    AccessMode mode)
+{
+    std::unique_ptr<_FileAsset> pAsset = util::make_unique<_FileAsset>();
+
+    status_t result = pAsset->openChunk(dataMap.get());
+    if (result != NO_ERROR) {
+        return NULL;
+    }
+
+    // We succeeded, so relinquish control of dataMap
+    (void) dataMap.release();
+    pAsset->mAccessMode = mode;
+    return std::move(pAsset);
+}
+
 /*
  * Create a new Asset from compressed data in a memory mapping.
  */
@@ -316,6 +333,21 @@
     return pAsset;
 }
 
+/*static*/ std::unique_ptr<Asset> Asset::createFromCompressedMap(std::unique_ptr<FileMap> dataMap,
+    size_t uncompressedLen, AccessMode mode)
+{
+  std::unique_ptr<_CompressedAsset> pAsset = util::make_unique<_CompressedAsset>();
+
+  status_t result = pAsset->openChunk(dataMap.get(), uncompressedLen);
+  if (result != NO_ERROR) {
+      return NULL;
+  }
+
+  // We succeeded, so relinquish control of dataMap
+  (void) dataMap.release();
+  pAsset->mAccessMode = mode;
+  return std::move(pAsset);
+}
 
 /*
  * Do generic seek() housekeeping.  Pass in the offset/whence values from
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index ef0c967..5667f92 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -138,6 +138,17 @@
   return &package_groups_[idx].dynamic_ref_table;
 }
 
+const DynamicRefTable* AssetManager2::GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const {
+  for (const PackageGroup& package_group : package_groups_) {
+    for (const ApkAssetsCookie& package_cookie : package_group.cookies_) {
+      if (package_cookie == cookie) {
+        return &package_group.dynamic_ref_table;
+      }
+    }
+  }
+  return nullptr;
+}
+
 void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
   const int diff = configuration_.diff(configuration);
   configuration_ = configuration;
@@ -188,6 +199,35 @@
   return OpenNonAsset(new_path, cookie, mode);
 }
 
+std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) {
+  ATRACE_CALL();
+
+  std::string full_path = "assets/" + dirname;
+  std::unique_ptr<SortedVector<AssetDir::FileInfo>> files =
+      util::make_unique<SortedVector<AssetDir::FileInfo>>();
+
+  // Start from the back.
+  for (auto iter = apk_assets_.rbegin(); iter != apk_assets_.rend(); ++iter) {
+    const ApkAssets* apk_assets = *iter;
+
+    auto func = [&](const StringPiece& name, FileType type) {
+      AssetDir::FileInfo info;
+      info.setFileName(String8(name.data(), name.size()));
+      info.setFileType(type);
+      info.setSourceName(String8(apk_assets->GetPath().c_str()));
+      files->add(info);
+    };
+
+    if (!apk_assets->ForEachFile(full_path, func)) {
+      return {};
+    }
+  }
+
+  std::unique_ptr<AssetDir> asset_dir = util::make_unique<AssetDir>();
+  asset_dir->setFileList(files.release());
+  return asset_dir;
+}
+
 // Search in reverse because that's how we used to do it and we need to preserve behaviour.
 // This is unfortunate, because ClassLoaders delegate to the parent first, so the order
 // is inconsistent for split APKs.
@@ -237,15 +277,15 @@
     desired_config = &density_override_config;
   }
 
-  const uint32_t package_id = get_package_id(resid);
-  const uint8_t type_id = get_type_id(resid);
-  const uint16_t entry_id = get_entry_id(resid);
-
-  if (type_id == 0) {
+  if (!is_valid_resid(resid)) {
     LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
     return kInvalidCookie;
   }
 
+  const uint32_t package_id = get_package_id(resid);
+  const uint8_t type_idx = get_type_id(resid) - 1;
+  const uint16_t entry_id = get_entry_id(resid);
+
   const uint8_t idx = package_ids_[package_id];
   if (idx == 0xff) {
     LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);
@@ -265,7 +305,7 @@
     uint32_t current_flags = 0;
 
     const LoadedPackage* loaded_package = package_group.packages_[i];
-    if (!loaded_package->FindEntry(type_id - 1, entry_id, *desired_config, &current_entry,
+    if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, &current_entry,
                                    &current_config, &current_flags)) {
       continue;
     }
@@ -385,16 +425,16 @@
 ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
                                                 ResTable_config* in_out_selected_config,
                                                 uint32_t* in_out_flags,
-                                                ResTable_ref* out_last_reference) {
+                                                uint32_t* out_last_reference) {
   ATRACE_CALL();
   constexpr const int kMaxIterations = 20;
 
-  out_last_reference->ident = 0u;
+  *out_last_reference = 0u;
   for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE &&
                               in_out_value->data != 0u && iteration < kMaxIterations;
        iteration++) {
     if (out_last_reference != nullptr) {
-      out_last_reference->ident = in_out_value->data;
+      *out_last_reference = in_out_value->data;
     }
     uint32_t new_flags = 0u;
     cookie = GetResource(in_out_value->data, true /*may_be_bag*/, 0u /*density_override*/,
@@ -405,7 +445,7 @@
     if (in_out_flags != nullptr) {
       *in_out_flags |= new_flags;
     }
-    if (out_last_reference->ident == in_out_value->data) {
+    if (*out_last_reference == in_out_value->data) {
       // This reference can't be resolved, so exit now and let the caller deal with it.
       return cookie;
     }
@@ -832,6 +872,25 @@
   return kInvalidCookie;
 }
 
+ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
+                                                 ResTable_config* in_out_selected_config,
+                                                 uint32_t* in_out_type_spec_flags,
+                                                 uint32_t* out_last_ref) {
+  if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) {
+    uint32_t new_flags;
+    cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags);
+    if (cookie == kInvalidCookie) {
+      return kInvalidCookie;
+    }
+
+    if (in_out_type_spec_flags != nullptr) {
+      *in_out_type_spec_flags |= new_flags;
+    }
+  }
+  return asset_manager_->ResolveReference(cookie, in_out_value, in_out_selected_config,
+                                          in_out_type_spec_flags, out_last_ref);
+}
+
 void Theme::Clear() {
   type_spec_flags_ = 0u;
   for (std::unique_ptr<Package>& package : packages_) {
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 763a178..9f241a0 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6532,6 +6532,8 @@
     return NO_ERROR;
 }
 
+DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {}
+
 DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
     : mAssignedPackageId(packageId)
     , mAppAsLib(appAsLib)
@@ -6637,11 +6639,11 @@
     // Do a proper lookup.
     uint8_t translatedId = mLookupTable[packageId];
     if (translatedId == 0) {
-        ALOGV("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
+        ALOGW("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
                 (uint8_t)mAssignedPackageId, (uint8_t)packageId);
         for (size_t i = 0; i < 256; i++) {
             if (mLookupTable[i] != 0) {
-                ALOGV("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]);
+                ALOGW("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]);
             }
         }
         return UNKNOWN_ERROR;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 202bc8e..575cd18 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -41,5 +41,31 @@
   }
 }
 
+std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+  ssize_t utf16_length =
+      utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
+  if (utf16_length <= 0) {
+    return {};
+  }
+
+  std::u16string utf16;
+  utf16.resize(utf16_length);
+  utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(), &*utf16.begin(),
+                utf16_length + 1);
+  return utf16;
+}
+
+std::string Utf16ToUtf8(const StringPiece16& utf16) {
+  ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
+  if (utf8_length <= 0) {
+    return {};
+  }
+
+  std::string utf8;
+  utf8.resize(utf8_length);
+  utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
+  return utf8;
+}
+
 } // namespace util
 } // namespace android
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 6d1578c..b7e66fb 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -25,6 +25,7 @@
 
 #include "androidfw/Asset.h"
 #include "androidfw/LoadedArsc.h"
+#include "androidfw/misc.h"
 
 namespace android {
 
@@ -38,6 +39,9 @@
   std::unique_ptr<Asset> Open(const std::string& path,
                               Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
 
+  bool ForEachFile(const std::string& path,
+                   const std::function<void(const StringPiece&, FileType)>& f) const;
+
   inline const std::string& GetPath() const { return path_; }
 
   inline const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); }
@@ -56,6 +60,7 @@
 
   using ZipArchivePtr =
       std::unique_ptr<typename std::remove_pointer<::ZipArchiveHandle>::type, ZipArchivePtrCloser>;
+
   ZipArchivePtr zip_handle_;
   std::string path_;
   std::unique_ptr<Asset> resources_asset_;
diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h
index 461e773..9d12a35 100644
--- a/libs/androidfw/include/androidfw/Asset.h
+++ b/libs/androidfw/include/androidfw/Asset.h
@@ -24,6 +24,8 @@
 #include <stdio.h>
 #include <sys/types.h>
 
+#include <memory>
+
 #include <utils/Compat.h>
 #include <utils/Errors.h>
 #include <utils/String8.h>
@@ -150,6 +152,7 @@
 
     /* AssetManager needs access to our "create" functions */
     friend class AssetManager;
+    friend class ApkAssets;
 
     /*
      * Create the asset from a named file on disk.
@@ -194,6 +197,9 @@
      */
     static Asset* createFromUncompressedMap(FileMap* dataMap, AccessMode mode);
 
+    static std::unique_ptr<Asset> createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,
+        AccessMode mode);
+
     /*
      * Create the asset from a memory-mapped file segment with compressed
      * data.
@@ -203,6 +209,9 @@
     static Asset* createFromCompressedMap(FileMap* dataMap,
         size_t uncompressedLen, AccessMode mode);
 
+    static std::unique_ptr<Asset> createFromCompressedMap(std::unique_ptr<FileMap> dataMap,
+        size_t uncompressedLen, AccessMode mode);
+
 
     /*
      * Create from a reference-counted chunk of shared memory.
diff --git a/libs/androidfw/include/androidfw/AssetDir.h b/libs/androidfw/include/androidfw/AssetDir.h
index bd89d7d..7aef02d 100644
--- a/libs/androidfw/include/androidfw/AssetDir.h
+++ b/libs/androidfw/include/androidfw/AssetDir.h
@@ -70,6 +70,7 @@
     const AssetDir& operator=(const AssetDir& src);
 
     friend class AssetManager;
+    friend class AssetManager2;
 
     /*
      * This holds information about files in the asset hierarchy.
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 81cdc46..d2bc6ee 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -107,6 +107,9 @@
   // Returns the DynamicRefTable for the given package ID.
   const DynamicRefTable* GetDynamicRefTableForPackage(uint32_t package_id) const;
 
+  // Returns the DynamicRefTable for the ApkAssets represented by the cookie.
+  const DynamicRefTable* GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const;
+
   // Sets/resets the configuration for this AssetManager. This will cause all
   // caches that are related to the configuration change to be invalidated.
   void SetConfiguration(const ResTable_config& configuration);
@@ -143,6 +146,11 @@
   std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie,
                               Asset::AccessMode mode);
 
+  // Opens the directory specified by `dirname`. The result is an AssetDir that is the combination
+  // of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded.
+  // The entries are sorted by their ASCII name.
+  std::unique_ptr<AssetDir> OpenDir(const std::string& dirname);
+
   // Searches the set of APKs loaded by this AssetManager and opens the first one found.
   // `mode` controls how the file is opened.
   // `out_cookie` is populated with the cookie of the APK this file was found in.
@@ -203,7 +211,7 @@
   // it was not found.
   ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
                                    ResTable_config* in_out_selected_config, uint32_t* in_out_flags,
-                                   ResTable_ref* out_last_reference);
+                                   uint32_t* out_last_reference);
 
   // Retrieves the best matching bag/map resource with ID `resid`.
   // This method will resolve all parent references for this bag and merge keys with the child.
@@ -298,6 +306,8 @@
 
   inline const AssetManager2* GetAssetManager() const { return asset_manager_; }
 
+  inline AssetManager2* GetAssetManager() { return asset_manager_; }
+
   // Returns a bit mask of configuration changes that will impact this
   // theme (and thus require completely reloading it).
   inline uint32_t GetChangingConfigurations() const { return type_spec_flags_; }
@@ -318,10 +328,10 @@
 
   // This is like AssetManager2::ResolveReference(), but also takes
   // care of resolving attribute references to the theme.
-  ApkAssetsCookie ResolveAttributeReference(Res_value* in_out_value, ApkAssetsCookie src_cookie,
-                                            uint32_t* out_last_ref = nullptr,
+  ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
+                                            ResTable_config* in_out_selected_config = nullptr,
                                             uint32_t* in_out_type_spec_flags = nullptr,
-                                            ResTable_config* out_selected_config = nullptr) const;
+                                            uint32_t* out_last_ref = nullptr);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(Theme);
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 04a5d95..cfe2652 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1554,7 +1554,7 @@
 {
     friend class AssetManager2;
 public:
-    DynamicRefTable() = default;
+    DynamicRefTable();
     DynamicRefTable(uint8_t packageId, bool appAsLib);
 
     // Loads an unmapped reference table from the package.
@@ -1577,10 +1577,10 @@
     }
 
 private:
-    uint8_t                         mAssignedPackageId = 0;
+    uint8_t                         mAssignedPackageId;
     uint8_t                         mLookupTable[256];
     KeyedVector<String16, uint8_t>  mEntries;
-    bool                            mAppAsLib = false;
+    bool                            mAppAsLib;
 };
 
 bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index 3950cf2..e4cd6a8 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -22,6 +22,8 @@
 
 #include "android-base/macros.h"
 
+#include "androidfw/StringPiece.h"
+
 namespace android {
 namespace util {
 
@@ -108,6 +110,12 @@
 
 void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
 
+// Converts a UTF-8 string to a UTF-16 string.
+std::u16string Utf8ToUtf16(const StringPiece& utf8);
+
+// Converts a UTF-16 string to a UTF-8 string.
+std::string Utf16ToUtf8(const StringPiece16& utf16);
+
 }  // namespace util
 }  // namespace android
 
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 6b4a719..c85b0b9 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -16,6 +16,9 @@
 
 #include "androidfw/ApkAssets.h"
 
+#include "android-base/file.h"
+#include "android-base/unique_fd.h"
+
 #include "TestHelpers.h"
 #include "data/basic/R.h"
 
@@ -51,4 +54,41 @@
   EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
 }
 
+TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
+  std::unique_ptr<const ApkAssets> loaded_apk =
+      ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+  ASSERT_NE(nullptr, loaded_apk);
+
+  {
+    std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
+    ASSERT_NE(nullptr, assets);
+  }
+
+  {
+    std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
+    ASSERT_NE(nullptr, assets);
+  }
+}
+
+TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
+  std::unique_ptr<const ApkAssets> loaded_apk =
+      ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+  ASSERT_NE(nullptr, loaded_apk);
+
+  auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN);
+  ASSERT_NE(nullptr, asset);
+
+  off64_t start, length;
+  base::unique_fd fd(asset->openFileDescriptor(&start, &length));
+  EXPECT_GE(fd.get(), 0);
+
+  lseek64(fd.get(), start, SEEK_SET);
+
+  std::string buffer;
+  buffer.resize(length);
+  ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length));
+
+  EXPECT_EQ("This should be uncompressed.\n\n", buffer);
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 78fbb0f..d8e5abf 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -312,12 +312,12 @@
   EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
   EXPECT_EQ(basic::R::integer::ref2, value.data);
 
-  ResTable_ref last_ref;
+  uint32_t last_ref;
   cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref);
   ASSERT_NE(kInvalidCookie, cookie);
   EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
   EXPECT_EQ(12000u, value.data);
-  EXPECT_EQ(basic::R::integer::ref2, last_ref.ident);
+  EXPECT_EQ(basic::R::integer::ref2, last_ref);
 }
 
 TEST_F(AssetManager2Test, ResolveReferenceToBag) {
@@ -335,12 +335,12 @@
   EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
   EXPECT_EQ(basic::R::array::integerArray1, value.data);
 
-  ResTable_ref last_ref;
+  uint32_t last_ref;
   cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref);
   ASSERT_NE(kInvalidCookie, cookie);
   EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
   EXPECT_EQ(basic::R::array::integerArray1, value.data);
-  EXPECT_EQ(basic::R::array::integerArray1, last_ref.ident);
+  EXPECT_EQ(basic::R::array::integerArray1, last_ref);
 }
 
 static bool IsConfigurationPresent(const std::set<ResTable_config>& configurations,
diff --git a/libs/androidfw/tests/data/basic/assets/uncompressed.txt b/libs/androidfw/tests/data/basic/assets/uncompressed.txt
new file mode 100644
index 0000000..c3d39c5
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/assets/uncompressed.txt
@@ -0,0 +1,2 @@
+This should be uncompressed.
+
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 7ee6734..0c17328 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build
index af0fd87..d619800 100755
--- a/libs/androidfw/tests/data/basic/build
+++ b/libs/androidfw/tests/data/basic/build
@@ -19,4 +19,11 @@
 
 PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
 
-aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES --split hdpi --split xhdpi --split xxhdpi --split fr,de -F basic.apk -f
+aapt package \
+    -M AndroidManifest.xml \
+    -S res \
+    -A assets \
+    -I $PATH_TO_FRAMEWORK_RES \
+    --split hdpi --split xhdpi --split xxhdpi --split fr,de \
+    -F basic.apk \
+    -f