Fail install when resources.arsc is compressed

If an application targets R+, prevent the application from being
installed if the app has a compressed resources.arsc or if the
resources.arsc is not aligned on a 4-byte boundary. Resources tables
that cannot be memory mapped have to be read into a buffer in RAM
and exert unnecessary memory pressure on the system.

Bug: 132742131
Test: manual (adding CTS tests)
Change-Id: Ieef764c87643863de24531fac12cc520fe6d90d0
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9a2e07e..9ca2db9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1550,6 +1550,16 @@
      */
     public static final int INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED = -123;
 
+    /**
+     * Installation failed return code: the {@code resources.arsc} of one of the APKs being
+     * installed is compressed or not aligned on a 4-byte boundary. Resource tables that cannot be
+     * memory mapped exert excess memory pressure on the system and drastically slow down
+     * construction of {@link Resources} objects.
+     *
+     * @hide
+     */
+    public static final int INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED = -124;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "DELETE_" }, value = {
             DELETE_KEEP_DATA,
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index c94d428..f884848 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -23,6 +23,7 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
 import static android.os.Build.VERSION_CODES.DONUT;
 import static android.os.Build.VERSION_CODES.O;
@@ -347,7 +348,20 @@
                                 + result.getErrorMessage());
             }
 
-            ParsingPackage pkg = result.getResult();
+            final ParsingPackage pkg = result.getResult();
+            if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.R
+                    && assets.containsAllocatedTable()) {
+                final ParseResult<?> deferResult = input.deferError(
+                        "Targeting R+ (version" + Build.VERSION_CODES.R + " and above) requires the"
+                                + " resources.arsc of installed APKs to be stored uncompressed and"
+                                + " aligned on a 4-byte boundary",
+                        DeferredError.RESOURCES_ARSC_COMPRESSED);
+                if (deferResult.isError()) {
+                    return input.error(INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED,
+                            deferResult.getErrorMessage());
+                }
+            }
+
             ApkAssets apkAssets = assets.getApkAssets()[0];
             if (apkAssets.definesOverlayable()) {
                 SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
diff --git a/core/java/android/content/pm/parsing/result/ParseInput.java b/core/java/android/content/pm/parsing/result/ParseInput.java
index 5385100..6b659be 100644
--- a/core/java/android/content/pm/parsing/result/ParseInput.java
+++ b/core/java/android/content/pm/parsing/result/ParseInput.java
@@ -59,6 +59,16 @@
         @ChangeId
         @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
         public static final long EMPTY_INTENT_ACTION_CATEGORY = 151163173;
+
+        /**
+         * The {@code resources.arsc} of one of the APKs being installed is compressed or not
+         * aligned on a 4-byte boundary. Resource tables that cannot be memory mapped exert excess
+         * memory pressure on the system and drastically slow down construction of
+         * {@link android.content.res.Resources} objects.
+         */
+        @ChangeId
+        @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+        public static final long RESOURCES_ARSC_COMPRESSED = 132742131;
     }
 
     <ResultType> ParseResult<ResultType> success(ResultType result);
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index d2103af..15a184f 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -819,6 +819,19 @@
         }
     }
 
+    /**
+     * Returns whether the {@code resources.arsc} of any loaded apk assets is allocated in RAM
+     * (not mmapped).
+     *
+     * @hide
+     */
+    public boolean containsAllocatedTable() {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeContainsAllocatedTable(mObject);
+        }
+    }
+
     CharSequence getPooledStringForCookie(int cookie, int id) {
         // Cookies map to ApkAssets starting at 1.
         return getApkAssets()[cookie - 1].getStringFromPool(id);
@@ -1482,6 +1495,7 @@
             long ptr, boolean includeOverlays, boolean includeLoaders);
 
     // File native methods.
+    private static native boolean nativeContainsAllocatedTable(long ptr);
     private static native @Nullable String[] nativeList(long ptr, @NonNull String path)
             throws IOException;
     private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index bf3fc57..12abc25 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -481,6 +481,11 @@
   return sparse_array;
 }
 
+static jboolean ContainsAllocatedTable(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  return assetmanager->ContainsAllocatedTable();
+}
+
 static jobjectArray NativeList(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring path) {
   ScopedUtfChars path_utf8(env, path);
   if (path_utf8.c_str() == nullptr) {
@@ -1495,6 +1500,7 @@
      (void*)NativeGetAssignedPackageIdentifiers},
 
     // AssetManager file methods.
+    {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
     {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
     {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
     {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index eaf452b..b9765ea 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -331,6 +331,11 @@
   return true;
 }
 
+bool AssetManager2::ContainsAllocatedTable() const {
+  return std::find_if(apk_assets_.begin(), apk_assets_.end(),
+                      std::mem_fn(&ApkAssets::IsTableAllocated)) != apk_assets_.end();
+}
+
 void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
   const int diff = configuration_.diff(configuration);
   configuration_ = configuration;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 879b050..e57490a 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -119,31 +119,36 @@
       package_property_t flags = 0U,
       std::unique_ptr<const AssetsProvider> override_asset = nullptr);
 
-  inline const std::string& GetPath() const {
+  const std::string& GetPath() const {
     return path_;
   }
 
-  inline const AssetsProvider* GetAssetsProvider() const {
+  const AssetsProvider* GetAssetsProvider() const {
     return assets_provider_.get();
   }
 
   // This is never nullptr.
-  inline const LoadedArsc* GetLoadedArsc() const {
+  const LoadedArsc* GetLoadedArsc() const {
     return loaded_arsc_.get();
   }
 
-  inline const LoadedIdmap* GetLoadedIdmap() const {
+  const LoadedIdmap* GetLoadedIdmap() const {
     return loaded_idmap_.get();
   }
 
-  inline bool IsLoader() const {
+  bool IsLoader() const {
     return (property_flags_ & PROPERTY_LOADER) != 0;
   }
 
-  inline bool IsOverlay() const {
+  bool IsOverlay() const {
     return loaded_idmap_ != nullptr;
   }
 
+  // Returns whether the resources.arsc is allocated in RAM (not mmapped).
+  bool IsTableAllocated() const {
+    return resources_asset_ && resources_asset_->isAllocated();
+  }
+
   bool IsUpToDate() const;
 
   // Creates an Asset from a file on disk.
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index e21abad..30ef25c 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -134,6 +134,9 @@
   const std::unordered_map<std::string, std::string>*
     GetOverlayableMapForPackage(uint32_t package_id) const;
 
+  // Returns whether the resources.arsc of any loaded apk assets is allocated in RAM (not mmapped).
+  bool ContainsAllocatedTable() 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);