Allow using reserved package IDs

Pre-O, the platform treats negative resource IDs [those with
a package ID of 0x80 or higher] as invalid.  In order to work
around this limitation, we allow the use of traditionally
reserved resource IDs [those between 0x02 and 0x7E].

Bug: 78041707
Test: ./out/host/linux-x86/nativetest64/aapt2_tests/aapt2_tests
Test: ./out/host/linux-x86/nativetest/libandroidfw_tests/libandroidfw_tests
Test: Manual. Create a feature split with a package ID of 0x7E and see it runs and can reference base resources.

Change-Id: I3d9782cc05d3a55e1a2467bf39566788847e1160
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 7d4e6f8..b3a3f45 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -3504,13 +3504,14 @@
 {
     PackageGroup(
             ResTable* _owner, const String16& _name, uint32_t _id,
-            bool appAsLib, bool _isSystemAsset)
+            bool appAsLib, bool _isSystemAsset, bool _isDynamic)
         : owner(_owner)
         , name(_name)
         , id(_id)
         , largestTypeId(0)
         , dynamicRefTable(static_cast<uint8_t>(_id), appAsLib)
         , isSystemAsset(_isSystemAsset)
+        , isDynamic(_isDynamic)
     { }
 
     ~PackageGroup() {
@@ -3614,6 +3615,7 @@
     // If the package group comes from a system asset. Used in
     // determining non-system locales.
     const bool                      isSystemAsset;
+    const bool isDynamic;
 };
 
 ResTable::Theme::Theme(const ResTable& table)
@@ -3982,6 +3984,11 @@
     return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1;
 }
 
+inline ssize_t ResTable::getResourcePackageIndexFromPackage(uint8_t packageID) const
+{
+    return ((ssize_t)mPackageMap[packageID])-1;
+}
+
 status_t ResTable::add(const void* data, size_t size, const int32_t cookie, bool copyData) {
     return addInternal(data, size, NULL, 0, false, cookie, copyData);
 }
@@ -4037,7 +4044,7 @@
     for (size_t i=0; i < src->mPackageGroups.size(); i++) {
         PackageGroup* srcPg = src->mPackageGroups[i];
         PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id,
-                false /* appAsLib */, isSystemAsset || srcPg->isSystemAsset);
+                false /* appAsLib */, isSystemAsset || srcPg->isSystemAsset, srcPg->isDynamic);
         for (size_t j=0; j<srcPg->packages.size(); j++) {
             pg->packages.add(srcPg->packages[j]);
         }
@@ -6277,6 +6284,68 @@
     return true;
 }
 
+bool ResTable::isPackageDynamic(uint8_t packageID) const {
+  if (mError != NO_ERROR) {
+      return false;
+  }
+  if (packageID == 0) {
+      ALOGW("Invalid package number 0x%08x", packageID);
+      return false;
+  }
+
+  const ssize_t p = getResourcePackageIndexFromPackage(packageID);
+
+  if (p < 0) {
+      ALOGW("Unknown package number 0x%08x", packageID);
+      return false;
+  }
+
+  const PackageGroup* const grp = mPackageGroups[p];
+  if (grp == NULL) {
+      ALOGW("Bad identifier for package number 0x%08x", packageID);
+      return false;
+  }
+
+  return grp->isDynamic;
+}
+
+bool ResTable::isResourceDynamic(uint32_t resID) const {
+    if (mError != NO_ERROR) {
+        return false;
+    }
+
+    const ssize_t p = getResourcePackageIndex(resID);
+    const int t = Res_GETTYPE(resID);
+    const int e = Res_GETENTRY(resID);
+
+    if (p < 0) {
+        if (Res_GETPACKAGE(resID)+1 == 0) {
+            ALOGW("No package identifier for resource number 0x%08x", resID);
+        } else {
+            ALOGW("No known package for resource number 0x%08x", resID);
+        }
+        return false;
+    }
+    if (t < 0) {
+        ALOGW("No type identifier for resource number 0x%08x", resID);
+        return false;
+    }
+
+    const PackageGroup* const grp = mPackageGroups[p];
+    if (grp == NULL) {
+        ALOGW("Bad identifier for resource number 0x%08x", resID);
+        return false;
+    }
+
+    Entry entry;
+    status_t err = getEntry(grp, t, e, NULL, &entry);
+    if (err != NO_ERROR) {
+        return false;
+    }
+
+    return grp->isDynamic;
+}
+
 static bool keyCompare(const ResTable_sparseTypeEntry& entry , uint16_t entryIdx) {
   return dtohs(entry.idx) < entryIdx;
 }
@@ -6520,12 +6589,14 @@
         id = targetPackageId;
     }
 
+    bool isDynamic = false;
     if (id >= 256) {
         LOG_ALWAYS_FATAL("Package id out of range");
         return NO_ERROR;
     } else if (id == 0 || (id == 0x7f && appAsLib) || isSystemAsset) {
         // This is a library or a system asset, so assign an ID
         id = mNextPackageId++;
+        isDynamic = true;
     }
 
     PackageGroup* group = NULL;
@@ -6553,10 +6624,9 @@
     size_t idx = mPackageMap[id];
     if (idx == 0) {
         idx = mPackageGroups.size() + 1;
-
         char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])];
         strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0]));
-        group = new PackageGroup(this, String16(tmpName), id, appAsLib, isSystemAsset);
+        group = new PackageGroup(this, String16(tmpName), id, appAsLib, isSystemAsset, isDynamic);
         if (group == NULL) {
             delete package;
             return (mError=NO_MEMORY);
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index bc0a07a..63b9e3a 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1716,6 +1716,18 @@
     bool getResourceFlags(uint32_t resID, uint32_t* outFlags) const;
 
     /**
+     * Returns whether or not the package for the given resource has been dynamically assigned.
+     * If the resource can't be found, returns 'false'.
+     */
+    bool isResourceDynamic(uint32_t resID) const;
+
+    /**
+     * Returns whether or not the given package has been dynamically assigned.
+     * If the package can't be found, returns 'false'.
+     */
+    bool isPackageDynamic(uint8_t packageID) const;
+
+    /**
      * Retrieve the value of a resource.  If the resource is found, returns a
      * value >= 0 indicating the table it is in (for use with
      * getTableStringBlock() and getTableCookie()) and fills in 'outValue'.  If
@@ -2024,6 +2036,7 @@
             bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset=false);
 
     ssize_t getResourcePackageIndex(uint32_t resID) const;
+    ssize_t getResourcePackageIndexFromPackage(uint8_t packageID) const;
 
     status_t getEntry(
         const PackageGroup* packageGroup, int typeIndex, int entryIndex,
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 6f213e1..34b46c5 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -117,8 +117,7 @@
 
 bool Reference::Flatten(android::Res_value* out_value) const {
   const ResourceId resid = id.value_or_default(ResourceId(0));
-  const bool dynamic = resid.is_valid_dynamic() && resid.package_id() != kFrameworkPackageId &&
-                       resid.package_id() < kAppPackageId;
+  const bool dynamic = resid.is_valid_dynamic() && is_dynamic;
 
   if (reference_type == Reference::Type::kResource) {
     if (dynamic) {
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 6371c4c..168ad61 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -157,6 +157,7 @@
   Maybe<ResourceId> id;
   Reference::Type reference_type;
   bool private_reference = false;
+  bool is_dynamic = false;
 
   Reference();
   explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index a3b7664..ea59722 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -129,6 +129,12 @@
   // Stable ID options.
   std::unordered_map<ResourceName, ResourceId> stable_id_map;
   Maybe<std::string> resource_id_map_path;
+
+  // When 'true', allow reserved package IDs to be used for applications. Pre-O, the platform
+  // treats negative resource IDs [those with a package ID of 0x80 or higher] as invalid.
+  // In order to work around this limitation, we allow the use of traditionally reserved
+  // resource IDs [those between 0x02 and 0x7E].
+  bool allow_reserved_package_id = false;
 };
 
 class LinkContext : public IAaptContext {
@@ -895,9 +901,7 @@
     // Capture the shared libraries so that the final resource table can be properly flattened
     // with support for shared libraries.
     for (auto& entry : asset_source->GetAssignedPackageIds()) {
-      if (entry.first > kFrameworkPackageId && entry.first < kAppPackageId) {
-        final_table_.included_packages_[entry.first] = entry.second;
-      } else if (entry.first == kAppPackageId) {
+      if (entry.first == kAppPackageId) {
         // Capture the included base feature package.
         included_feature_base_ = entry.second;
       } else if (entry.first == kFrameworkPackageId) {
@@ -911,6 +915,8 @@
           // android:versionCode from the framework AndroidManifest.xml.
           ExtractCompileSdkVersions(asset_source->GetAssetManager());
         }
+      } else if (asset_source->IsPackageDynamic(entry.first)) {
+        final_table_.included_packages_[entry.first] = entry.second;
       }
     }
 
@@ -1595,7 +1601,15 @@
     // If required, the package name is modifed before flattening, and then modified back
     // to its original name.
     ResourceTablePackage* package_to_rewrite = nullptr;
-    if (context_->GetPackageId() > kAppPackageId &&
+    // Pre-O, the platform treats negative resource IDs [those with a package ID of 0x80
+    // or higher] as invalid. In order to work around this limitation, we allow the use
+    // of traditionally reserved resource IDs [those between 0x02 and 0x7E]. Allow the
+    // definition of what a valid "split" package ID is to account for this.
+    const bool isSplitPackage = (options_.allow_reserved_package_id &&
+          context_->GetPackageId() != kAppPackageId &&
+          context_->GetPackageId() != kFrameworkPackageId)
+        || (!options_.allow_reserved_package_id && context_->GetPackageId() > kAppPackageId);
+    if (isSplitPackage &&
         included_feature_base_ == make_value(context_->GetCompilationPackage())) {
       // The base APK is included, and this is a feature split. If the base package is
       // the same as this package, then we are building an old style Android Instant Apps feature
@@ -2139,6 +2153,10 @@
                         "Generates a text file containing the resource symbols of the R class in\n"
                         "the specified folder.",
                         &options.generate_text_symbols_path)
+          .OptionalSwitch("--allow-reserved-package-id",
+                          "Allows the use of a reserved package ID. This should on be used for\n"
+                          "packages with a pre-O min-sdk\n",
+                          &options.allow_reserved_package_id)
           .OptionalSwitch("--auto-add-overlay",
                           "Allows the addition of new resources in overlays without\n"
                           "<add-resource> tags.",
@@ -2238,7 +2256,9 @@
     }
 
     const uint32_t package_id_int = maybe_package_id_int.value();
-    if (package_id_int < kAppPackageId || package_id_int > std::numeric_limits<uint8_t>::max()) {
+    if (package_id_int > std::numeric_limits<uint8_t>::max()
+        || package_id_int == kFrameworkPackageId
+        || (!options.allow_reserved_package_id && package_id_int < kAppPackageId)) {
       context.GetDiagnostics()->Error(
           DiagMessage() << StringPrintf(
               "invalid package ID 0x%02x. Must be in the range 0x7f-0xff.", package_id_int));
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 9aaaa69..3a5d585 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -340,6 +340,7 @@
     // against libraries without assigned IDs.
     // Ex: Linking against own resources when building a static library.
     reference->id = s->id;
+    reference->is_dynamic = s->is_dynamic;
     return true;
   }
 
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 2e97a2f..fc4c9b5 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -227,6 +227,10 @@
   return package_map;
 }
 
+bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
+  return assets_.getResources(false).isPackageDynamic(packageId);
+}
+
 static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable(
     const android::ResTable& table, ResourceId id) {
   // Try as a bag.
@@ -330,6 +334,7 @@
   } else {
     s = util::make_unique<SymbolTable::Symbol>();
     s->id = res_id;
+    s->is_dynamic = table.isResourceDynamic(res_id.id);
   }
 
   if (s) {
@@ -354,7 +359,6 @@
     // Exit early and avoid the error logs from AssetManager.
     return {};
   }
-
   const android::ResTable& table = assets_.getResources(false);
   Maybe<ResourceName> maybe_name = GetResourceName(table, id);
   if (!maybe_name) {
@@ -370,6 +374,7 @@
   } else {
     s = util::make_unique<SymbolTable::Symbol>();
     s->id = id;
+    s->is_dynamic = table.isResourceDynamic(id.id);
   }
 
   if (s) {
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index b676efb..51a2e373 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -68,6 +68,7 @@
     Maybe<ResourceId> id;
     std::shared_ptr<Attribute> attribute;
     bool is_public = false;
+    bool is_dynamic = false;
   };
 
   SymbolTable(NameMangler* mangler);
@@ -192,6 +193,7 @@
 
   bool AddAssetPath(const android::StringPiece& path);
   std::map<size_t, std::string> GetAssignedPackageIds() const;
+  bool IsPackageDynamic(uint32_t packageId) const;
 
   std::unique_ptr<SymbolTable::Symbol> FindByName(
       const ResourceName& name) override;