AssetManager2: Add GetResourceId

Add ability to lookup a resource by name.

Test: make libandroidfw_tests
Change-Id: I262ba5ce4c9892458226fbdb44cf21f9877fb92d
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index ecf6bd4..e764034 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -36,6 +36,7 @@
         "misc.cpp",
         "ObbFile.cpp",
         "ResourceTypes.cpp",
+        "ResourceUtils.cpp",
         "StreamingZipInflater.cpp",
         "TypeWrappers.cpp",
         "Util.cpp",
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 542a125..ef0c967 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -31,6 +31,8 @@
 #endif
 #endif
 
+#include "androidfw/ResourceUtils.h"
+
 namespace android {
 
 AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); }
@@ -235,9 +237,9 @@
     desired_config = &density_override_config;
   }
 
-  const uint32_t package_id = util::get_package_id(resid);
-  const uint8_t type_id = util::get_type_id(resid);
-  const uint16_t entry_id = util::get_entry_id(resid);
+  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) {
     LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
@@ -452,7 +454,7 @@
     ResolvedBag::Entry* new_entry = new_bag->entries;
     for (; map_entry != map_entry_end; ++map_entry) {
       uint32_t new_key = dtohl(map_entry->name.ident);
-      if (!util::is_internal_resid(new_key)) {
+      if (!is_internal_resid(new_key)) {
         // Attributes, arrays, etc don't have a resource id as the name. They specify
         // other data, which would be wrong to change via a lookup.
         if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
@@ -501,7 +503,7 @@
   // The keys are expected to be in sorted order. Merge the two bags.
   while (map_entry != map_entry_end && parent_entry != parent_entry_end) {
     uint32_t child_key = dtohl(map_entry->name.ident);
-    if (!util::is_internal_resid(child_key)) {
+    if (!is_internal_resid(child_key)) {
       if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) {
         LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid);
         return nullptr;
@@ -533,7 +535,7 @@
   // Finish the child entries if they exist.
   while (map_entry != map_entry_end) {
     uint32_t new_key = dtohl(map_entry->name.ident);
-    if (!util::is_internal_resid(new_key)) {
+    if (!is_internal_resid(new_key)) {
       if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
         LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid);
         return nullptr;
@@ -571,12 +573,71 @@
   return result;
 }
 
+static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) {
+  ssize_t len =
+      utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size(), false);
+  if (len < 0) {
+    return false;
+  }
+  out->resize(static_cast<size_t>(len));
+  utf8_to_utf16(reinterpret_cast<const uint8_t*>(str.data()), str.size(), &*out->begin(),
+                static_cast<size_t>(len + 1));
+  return true;
+}
+
 uint32_t AssetManager2::GetResourceId(const std::string& resource_name,
                                       const std::string& fallback_type,
                                       const std::string& fallback_package) {
-  (void)resource_name;
-  (void)fallback_type;
-  (void)fallback_package;
+  StringPiece package_name, type, entry;
+  if (!ExtractResourceName(resource_name, &package_name, &type, &entry)) {
+    return 0u;
+  }
+
+  if (entry.empty()) {
+    return 0u;
+  }
+
+  if (package_name.empty()) {
+    package_name = fallback_package;
+  }
+
+  if (type.empty()) {
+    type = fallback_type;
+  }
+
+  std::u16string type16;
+  if (!Utf8ToUtf16(type, &type16)) {
+    return 0u;
+  }
+
+  std::u16string entry16;
+  if (!Utf8ToUtf16(entry, &entry16)) {
+    return 0u;
+  }
+
+  const StringPiece16 kAttr16 = u"attr";
+  const static std::u16string kAttrPrivate16 = u"^attr-private";
+
+  for (const PackageGroup& package_group : package_groups_) {
+    for (const LoadedPackage* package : package_group.packages_) {
+      if (package_name != package->GetPackageName()) {
+        // All packages in the same group are expected to have the same package name.
+        break;
+      }
+
+      uint32_t resid = package->FindEntryByName(type16, entry16);
+      if (resid == 0u && kAttr16 == type16) {
+        // Private attributes in libraries (such as the framework) are sometimes encoded
+        // under the type '^attr-private' in order to leave the ID space of public 'attr'
+        // free for future additions. Check '^attr-private' for the same name.
+        resid = package->FindEntryByName(kAttrPrivate16, entry16);
+      }
+
+      if (resid != 0u) {
+        return fix_package_id(resid, package_group.dynamic_ref_table.mAssignedPackageId);
+      }
+    }
+  }
   return 0u;
 }
 
@@ -619,15 +680,15 @@
 
     // If the resource ID passed in is not a style, the key can be
     // some other identifier that is not a resource ID.
-    if (!util::is_valid_resid(attr_resid)) {
+    if (!is_valid_resid(attr_resid)) {
       return false;
     }
 
-    const uint32_t package_idx = util::get_package_id(attr_resid);
+    const uint32_t package_idx = get_package_id(attr_resid);
 
     // The type ID is 1-based, so subtract 1 to get an index.
-    const uint32_t type_idx = util::get_type_id(attr_resid) - 1;
-    const uint32_t entry_idx = util::get_entry_id(attr_resid);
+    const uint32_t type_idx = get_type_id(attr_resid) - 1;
+    const uint32_t entry_idx = get_entry_id(attr_resid);
 
     std::unique_ptr<Package>& package = packages_[package_idx];
     if (package == nullptr) {
@@ -656,9 +717,9 @@
   // and populate the structures.
   for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
     const uint32_t attr_resid = bag_iter->key;
-    const uint32_t package_idx = util::get_package_id(attr_resid);
-    const uint32_t type_idx = util::get_type_id(attr_resid) - 1;
-    const uint32_t entry_idx = util::get_entry_id(attr_resid);
+    const uint32_t package_idx = get_package_id(attr_resid);
+    const uint32_t type_idx = get_type_id(attr_resid) - 1;
+    const uint32_t entry_idx = get_entry_id(attr_resid);
     Package* package = packages_[package_idx].get();
     util::unique_cptr<Type>& type = package->types[type_idx];
     if (type->entry_count != type->entry_capacity) {
@@ -691,15 +752,15 @@
   uint32_t type_spec_flags = 0u;
 
   for (int iterations_left = kMaxIterations; iterations_left > 0; iterations_left--) {
-    if (!util::is_valid_resid(resid)) {
+    if (!is_valid_resid(resid)) {
       return kInvalidCookie;
     }
 
-    const uint32_t package_idx = util::get_package_id(resid);
+    const uint32_t package_idx = get_package_id(resid);
 
     // Type ID is 1-based, subtract 1 to get the index.
-    const uint32_t type_idx = util::get_type_id(resid) - 1;
-    const uint32_t entry_idx = util::get_entry_id(resid);
+    const uint32_t type_idx = get_type_id(resid) - 1;
+    const uint32_t entry_idx = get_entry_id(resid);
 
     const Package* package = packages_[package_idx].get();
     if (package == nullptr) {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index cb589ec..db72f48 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -34,6 +34,7 @@
 
 #include "androidfw/ByteBucketArray.h"
 #include "androidfw/Chunk.h"
+#include "androidfw/ResourceUtils.h"
 #include "androidfw/Util.h"
 
 using android::base::StringPrintf;
@@ -181,9 +182,9 @@
                            LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
                            uint32_t* out_flags) const {
   ATRACE_CALL();
-  const uint8_t package_id = util::get_package_id(resid);
-  const uint8_t type_id = util::get_type_id(resid);
-  const uint16_t entry_id = util::get_entry_id(resid);
+  const uint8_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) {
     LOG(ERROR) << "Invalid ID 0x" << std::hex << resid << std::dec << ".";
@@ -200,7 +201,7 @@
 }
 
 const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
-  const uint8_t package_id = util::get_package_id(resid);
+  const uint8_t package_id = get_package_id(resid);
   for (const auto& loaded_package : packages_) {
     if (loaded_package->package_id_ == package_id) {
       return loaded_package.get();
@@ -372,6 +373,45 @@
   }
 }
 
+uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name,
+                                        const std::u16string& entry_name) const {
+  ssize_t type_idx = type_string_pool_.indexOfString(type_name.data(), type_name.size());
+  if (type_idx < 0) {
+    return 0u;
+  }
+
+  ssize_t key_idx = key_string_pool_.indexOfString(entry_name.data(), entry_name.size());
+  if (key_idx < 0) {
+    return 0u;
+  }
+
+  const TypeSpec* type_spec = type_specs_[type_idx].get();
+  if (type_spec == nullptr) {
+    return 0u;
+  }
+
+  for (size_t ti = 0; ti < type_spec->type_count; ti++) {
+    const Type* type = &type_spec->types[ti];
+    size_t entry_count = dtohl(type->type->entryCount);
+    for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
+      const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+          reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
+      const uint32_t offset = dtohl(entry_offsets[entry_idx]);
+      if (offset != ResTable_type::NO_ENTRY) {
+        const ResTable_entry* entry =
+            reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type->type) +
+                                                    dtohl(type->type->entriesStart) + offset);
+        if (dtohl(entry->key.index) == static_cast<uint32_t>(key_idx)) {
+          // The package ID will be overridden by the caller (due to runtime assignment of package
+          // IDs for shared libraries).
+          return make_resid(0x00, type_idx + type_id_offset_ + 1, entry_idx);
+        }
+      }
+    }
+  }
+  return 0u;
+}
+
 std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
   ATRACE_CALL();
   std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()};
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
new file mode 100644
index 0000000..1aa6cf6
--- /dev/null
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidfw/ResourceUtils.h"
+
+namespace android {
+
+bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+                         StringPiece* out_entry) {
+  *out_package = "";
+  *out_type = "";
+  bool has_package_separator = false;
+  bool has_type_separator = false;
+  const char* start = str.data();
+  const char* end = start + str.size();
+  const char* current = start;
+  while (current != end) {
+    if (out_type->size() == 0 && *current == '/') {
+      has_type_separator = true;
+      out_type->assign(start, current - start);
+      start = current + 1;
+    } else if (out_package->size() == 0 && *current == ':') {
+      has_package_separator = true;
+      out_package->assign(start, current - start);
+      start = current + 1;
+    }
+    current++;
+  }
+  out_entry->assign(start, end - start);
+
+  return !(has_package_separator && out_package->empty()) &&
+         !(has_type_separator && out_type->empty());
+}
+
+}  // namespace android
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 91a7cb7..f30b158 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -101,6 +101,13 @@
   // before being inserted into the set. This may cause some equivalent locales to de-dupe.
   void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const;
 
+  // Finds the entry with the specified type name and entry name. The names are in UTF-16 because
+  // the underlying ResStringPool API expects this. For now this is acceptable, but since
+  // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
+  // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible
+  // for patching the correct package ID to the resource ID.
+  uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
 
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
new file mode 100644
index 0000000..6bf7c24
--- /dev/null
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROIDFW_RESOURCEUTILS_H
+#define ANDROIDFW_RESOURCEUTILS_H
+
+#include "androidfw/StringPiece.h"
+
+namespace android {
+
+// Extracts the package, type, and name from a string of the format: [[package:]type/]name
+// Validation must be performed on each extracted piece.
+// Returns false if there was a syntax error.
+bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+                         StringPiece* out_entry);
+
+inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) {
+  return resid | (static_cast<uint32_t>(package_id) << 24);
+}
+
+inline uint8_t get_package_id(uint32_t resid) {
+  return static_cast<uint8_t>((resid >> 24) & 0x000000ffu);
+}
+
+// The type ID is 1-based, so if the returned value is 0 it is invalid.
+inline uint8_t get_type_id(uint32_t resid) {
+  return static_cast<uint8_t>((resid >> 16) & 0x000000ffu);
+}
+
+inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); }
+
+inline bool is_internal_resid(uint32_t resid) {
+  return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0;
+}
+
+inline bool is_valid_resid(uint32_t resid) {
+  return (resid & 0x00ff0000u) != 0 && (resid & 0xff000000u) != 0;
+}
+
+inline uint32_t make_resid(uint8_t package_id, uint8_t type_id, uint16_t entry_id) {
+  return (static_cast<uint32_t>(package_id) << 24) | (static_cast<uint32_t>(type_id) << 16) |
+         entry_id;
+}
+
+}  // namespace android
+
+#endif /* ANDROIDFW_RESOURCEUTILS_H */
diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h
index c9effd1..8f6824b 100644
--- a/libs/androidfw/include/androidfw/StringPiece.h
+++ b/libs/androidfw/include/androidfw/StringPiece.h
@@ -257,6 +257,16 @@
   return data_ + length_;
 }
 
+template <typename TChar>
+inline bool operator==(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
+  return BasicStringPiece<TChar>(lhs) == rhs;
+}
+
+template <typename TChar>
+inline bool operator!=(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
+  return BasicStringPiece<TChar>(lhs) != rhs;
+}
+
 inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
   return out.write(str.data(), str.size());
 }
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index 96b42bf..3950cf2 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -106,29 +106,6 @@
   pointer ptr_;
 };
 
-inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) {
-  return resid | (static_cast<uint32_t>(package_id) << 24);
-}
-
-inline uint8_t get_package_id(uint32_t resid) {
-  return static_cast<uint8_t>((resid >> 24) & 0x000000ffu);
-}
-
-// The type ID is 1-based, so if the returned value is 0 it is invalid.
-inline uint8_t get_type_id(uint32_t resid) {
-  return static_cast<uint8_t>((resid >> 16) & 0x000000ffu);
-}
-
-inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); }
-
-inline bool is_internal_resid(uint32_t resid) {
-  return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0;
-}
-
-inline bool is_valid_resid(uint32_t resid) {
-  return (resid & 0x00ff0000u) != 0 && (resid & 0xff000000u) != 0;
-}
-
 void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
 
 }  // namespace util
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index 650f813..19527c5 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -32,6 +32,7 @@
     ConfigLocale_test.cpp \
     Idmap_test.cpp \
     LoadedArsc_test.cpp \
+    ResourceUtils_test.cpp \
     ResTable_test.cpp \
     Split_test.cpp \
     StringPiece_test.cpp \
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 557d8d4..78fbb0f 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -20,6 +20,7 @@
 #include "android-base/logging.h"
 
 #include "TestHelpers.h"
+#include "androidfw/ResourceUtils.h"
 #include "data/appaslib/R.h"
 #include "data/basic/R.h"
 #include "data/lib_one/R.h"
@@ -194,11 +195,11 @@
   ResTable_config selected_config;
   uint32_t flags;
   ApkAssetsCookie cookie = assetmanager.GetResource(
-      util::fix_package_id(appaslib::R::integer::number1, 0x02), false /*may_be_bag*/,
+      fix_package_id(appaslib::R::integer::number1, 0x02), false /*may_be_bag*/,
       0u /*density_override*/, &value, &selected_config, &flags);
   ASSERT_NE(kInvalidCookie, cookie);
   EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
-  EXPECT_EQ(util::fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
+  EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
 }
 
 TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) {
@@ -238,9 +239,9 @@
 
   // First two attributes come from lib_one.
   EXPECT_EQ(1, bag->entries[0].cookie);
-  EXPECT_EQ(0x03, util::get_package_id(bag->entries[0].key));
+  EXPECT_EQ(0x03, get_package_id(bag->entries[0].key));
   EXPECT_EQ(1, bag->entries[1].cookie);
-  EXPECT_EQ(0x03, util::get_package_id(bag->entries[1].key));
+  EXPECT_EQ(0x03, get_package_id(bag->entries[1].key));
 }
 
 TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) {
diff --git a/libs/androidfw/tests/ResourceUtils_test.cpp b/libs/androidfw/tests/ResourceUtils_test.cpp
new file mode 100644
index 0000000..b64a884
--- /dev/null
+++ b/libs/androidfw/tests/ResourceUtils_test.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidfw/ResourceUtils.h"
+
+#include "TestHelpers.h"
+
+namespace android {
+
+TEST(ResourceUtilsTest, ExtractResourceName) {
+  StringPiece package, type, entry;
+  ASSERT_TRUE(ExtractResourceName("android:string/foo", &package, &type, &entry));
+  EXPECT_EQ("android", package);
+  EXPECT_EQ("string", type);
+  EXPECT_EQ("foo", entry);
+
+  ASSERT_TRUE(ExtractResourceName("string/foo", &package, &type, &entry));
+  EXPECT_EQ("", package);
+  EXPECT_EQ("string", type);
+  EXPECT_EQ("foo", entry);
+
+  ASSERT_TRUE(ExtractResourceName("foo", &package, &type, &entry));
+  EXPECT_EQ("", package);
+  EXPECT_EQ("", type);
+  EXPECT_EQ("foo", entry);
+
+  ASSERT_TRUE(ExtractResourceName("android:foo", &package, &type, &entry));
+  EXPECT_EQ("android", package);
+  EXPECT_EQ("", type);
+  EXPECT_EQ("foo", entry);
+
+  EXPECT_FALSE(ExtractResourceName(":string/foo", &package, &type, &entry));
+  EXPECT_FALSE(ExtractResourceName("/foo", &package, &type, &entry));
+}
+
+}  // namespace android
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index daed28b..dfff9c0 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -19,6 +19,7 @@
 #include "android-base/logging.h"
 
 #include "TestHelpers.h"
+#include "androidfw/ResourceUtils.h"
 #include "data/lib_one/R.h"
 #include "data/libclient/R.h"
 #include "data/styles/R.h"
@@ -215,9 +216,9 @@
   EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
 
   // lib_one is assigned package ID 0x03.
-  EXPECT_EQ(3u, util::get_package_id(value.data));
-  EXPECT_EQ(util::get_type_id(lib_one::R::string::foo), util::get_type_id(value.data));
-  EXPECT_EQ(util::get_entry_id(lib_one::R::string::foo), util::get_entry_id(value.data));
+  EXPECT_EQ(3u, get_package_id(value.data));
+  EXPECT_EQ(get_type_id(lib_one::R::string::foo), get_type_id(value.data));
+  EXPECT_EQ(get_entry_id(lib_one::R::string::foo), get_entry_id(value.data));
 }
 
 TEST_F(ThemeTest, CopyThemeSameAssetManager) {
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 1123967..150dc58 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -19,6 +19,7 @@
 #include <sstream>
 
 #include "androidfw/ResourceTypes.h"
+#include "androidfw/ResourceUtils.h"
 
 #include "NameMangler.h"
 #include "SdkConstants.h"
@@ -69,31 +70,6 @@
   return name_out;
 }
 
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package,
-                         StringPiece* out_type, StringPiece* out_entry) {
-  bool has_package_separator = false;
-  bool has_type_separator = false;
-  const char* start = str.data();
-  const char* end = start + str.size();
-  const char* current = start;
-  while (current != end) {
-    if (out_type->size() == 0 && *current == '/') {
-      has_type_separator = true;
-      out_type->assign(start, current - start);
-      start = current + 1;
-    } else if (out_package->size() == 0 && *current == ':') {
-      has_package_separator = true;
-      out_package->assign(start, current - start);
-      start = current + 1;
-    }
-    current++;
-  }
-  out_entry->assign(start, end - start);
-
-  return !(has_package_separator && out_package->empty()) &&
-         !(has_type_separator && out_type->empty());
-}
-
 bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref,
                        bool* out_private) {
   if (str.empty()) {
@@ -110,8 +86,8 @@
   StringPiece package;
   StringPiece type;
   StringPiece entry;
-  if (!ExtractResourceName(str.substr(offset, str.size() - offset), &package,
-                           &type, &entry)) {
+  if (!android::ExtractResourceName(str.substr(offset, str.size() - offset), &package, &type,
+                                    &entry)) {
     return false;
   }
 
@@ -197,8 +173,8 @@
     StringPiece package;
     StringPiece type;
     StringPiece entry;
-    if (!ExtractResourceName(trimmed_str.substr(1, trimmed_str.size() - 1),
-                             &package, &type, &entry)) {
+    if (!android::ExtractResourceName(trimmed_str.substr(1, trimmed_str.size() - 1), &package,
+                                      &type, &entry)) {
       return false;
     }
 
@@ -258,7 +234,7 @@
   ref.type = ResourceType::kStyle;
 
   StringPiece type_str;
-  ExtractResourceName(name, &ref.package, &type_str, &ref.entry);
+  android::ExtractResourceName(name, &ref.package, &type_str, &ref.entry);
   if (!type_str.empty()) {
     // If we have a type, make sure it is a Style.
     const ResourceType* parsed_type = ParseResourceType(type_str);
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index bd3a8e3..59b78f4 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -29,18 +29,6 @@
 namespace aapt {
 namespace ResourceUtils {
 
-/*
- * Extracts the package, type, and name from a string of the format:
- *
- *      [package:]type/name
- *
- * where the package can be empty. Validation must be performed on each
- * individual extracted piece to verify that the pieces are valid.
- * Returns false if there was no package but a ':' was present.
- */
-bool ExtractResourceName(const android::StringPiece& str, android::StringPiece* out_package,
-                         android::StringPiece* out_type, android::StringPiece* out_entry);
-
 /**
  * Returns true if the string was parsed as a resource name
  * ([*][package:]type/name), with