Merge "Suppress InvalidPatternSyntax issue instead of demoting" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ce3e985..b54022b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -64,6 +64,7 @@
     ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
     ":android.tracing.flags-aconfig-java{.generated_srcjars}",
     ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
+    ":android.webkit.flags-aconfig-java{.generated_srcjars}",
 ]
 
 filegroup {
@@ -762,3 +763,19 @@
     aconfig_declarations: "android.appwidget.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// WebView
+aconfig_declarations {
+    name: "android.webkit.flags-aconfig",
+    package: "android.webkit",
+    srcs: [
+        "core/java/android/webkit/*.aconfig",
+        "services/core/java/com/android/server/webkit/*.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "android.webkit.flags-aconfig-java",
+    aconfig_declarations: "android.webkit.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index c42c7ca..2ace602 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -128,6 +128,7 @@
         final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration();
         final InsetsState mOutInsetsState = new InsetsState();
         final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array();
+        final Bundle mOutBundle = new Bundle();
         final IWindow mWindow;
         final View mView;
         final WindowManager.LayoutParams mParams;
@@ -136,7 +137,7 @@
         final SurfaceControl mOutSurfaceControl;
 
         final IntSupplier mViewVisibility;
-
+        int mRelayoutSeq;
         int mFlags;
 
         RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) {
@@ -152,10 +153,11 @@
         void runBenchmark(BenchmarkState state) throws RemoteException {
             final IWindowSession session = WindowManagerGlobal.getWindowSession();
             while (state.keepRunning()) {
+                mRelayoutSeq++;
                 session.relayout(mWindow, mParams, mWidth, mHeight,
-                        mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */,
+                        mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */,
                         mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState,
-                        mOutControls, new Bundle());
+                        mOutControls, mOutBundle);
             }
         }
     }
diff --git a/api/Android.bp b/api/Android.bp
index d6c14fb..2b1cfcb 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -404,3 +404,49 @@
     "ApiDocs.bp",
     "StubLibraries.bp",
 ]
+
+genrule_defaults {
+    name: "flag-api-mapping-generation-defaults",
+    cmd: "$(location extract-flagged-apis) $(in) $(out)",
+    tools: ["extract-flagged-apis"],
+}
+
+genrule {
+    name: "flag-api-mapping-PublicApi",
+    defaults: ["flag-api-mapping-generation-defaults"],
+    srcs: [":frameworks-base-api-current.txt"],
+    out: ["flag_api_map.textproto"],
+    dist: {
+        targets: ["droid"],
+    },
+}
+
+genrule {
+    name: "flag-api-mapping-SystemApi",
+    defaults: ["flag-api-mapping-generation-defaults"],
+    srcs: [":frameworks-base-api-system-current.txt"],
+    out: ["system_flag_api_map.textproto"],
+    dist: {
+        targets: ["droid"],
+    },
+}
+
+genrule {
+    name: "flag-api-mapping-ModuleLibApi",
+    defaults: ["flag-api-mapping-generation-defaults"],
+    srcs: [":frameworks-base-api-module-lib-current.txt"],
+    out: ["module_lib_flag_api_map.textproto"],
+    dist: {
+        targets: ["droid"],
+    },
+}
+
+genrule {
+    name: "flag-api-mapping-SystemServerApi",
+    defaults: ["flag-api-mapping-generation-defaults"],
+    srcs: [":frameworks-base-api-system-server-current.txt"],
+    out: ["system_server_flag_api_map.textproto"],
+    dist: {
+        targets: ["droid"],
+    },
+}
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 948e64f..9ffb704 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -25,7 +25,7 @@
     var cb = ApiFile.parseApi(listOf(File(args[0])))
     val flagToApi = mutableMapOf<String, MutableList<String>>()
     cb.getPackages()
-        .allTopLevelClasses()
+        .allClasses()
         .filter { it.methods().size > 0 }
         .forEach {
             for (method in it.methods()) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 207abb2..812d4cd6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12781,6 +12781,7 @@
     method public boolean isPackageSuspended();
     method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String);
     method public abstract boolean isSafeMode();
+    method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull String, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
     method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
@@ -36759,6 +36760,7 @@
     field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
     field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
     field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS";
+    field @FlaggedApi("android.app.modes_api") public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
     field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS";
     field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
     field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
@@ -36846,6 +36848,7 @@
     field public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
     field public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
     field public static final String EXTRA_AUTHORITIES = "authorities";
+    field @FlaggedApi("android.app.modes_api") public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
     field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
     field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED";
     field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST";
@@ -45159,6 +45162,7 @@
     method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
     method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
+    method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
     method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
     method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
@@ -54183,7 +54187,7 @@
     method public boolean isEnabled();
     method public boolean isFocusable();
     method public boolean isFocused();
-    method public boolean isGranularScrollingSupported();
+    method @FlaggedApi("android.view.accessibility.granular_scrolling") public boolean isGranularScrollingSupported();
     method public boolean isHeading();
     method public boolean isImportantForAccessibility();
     method public boolean isLongClickable();
@@ -54233,7 +54237,7 @@
     method public void setError(CharSequence);
     method public void setFocusable(boolean);
     method public void setFocused(boolean);
-    method public void setGranularScrollingSupported(boolean);
+    method @FlaggedApi("android.view.accessibility.granular_scrolling") public void setGranularScrollingSupported(boolean);
     method public void setHeading(boolean);
     method public void setHintText(CharSequence);
     method public void setImportantForAccessibility(boolean);
@@ -54288,7 +54292,7 @@
     field public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT = "android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT";
     field public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
     field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
-    field public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
+    field @FlaggedApi("android.view.accessibility.granular_scrolling") public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
     field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
     field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
     field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
@@ -54391,10 +54395,9 @@
   public static final class AccessibilityNodeInfo.CollectionInfo {
     ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean);
     ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int);
-    ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int, int, int);
     method public int getColumnCount();
-    method public int getImportantForAccessibilityItemCount();
-    method public int getItemCount();
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getImportantForAccessibilityItemCount();
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getItemCount();
     method public int getRowCount();
     method public int getSelectionMode();
     method public boolean isHierarchical();
@@ -54403,18 +54406,18 @@
     field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
     field public static final int SELECTION_MODE_NONE = 0; // 0x0
     field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
-    field public static final int UNDEFINED = -1; // 0xffffffff
+    field @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final int UNDEFINED = -1; // 0xffffffff
   }
 
-  public static final class AccessibilityNodeInfo.CollectionInfo.Builder {
-    ctor public AccessibilityNodeInfo.CollectionInfo.Builder();
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build();
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int);
+  @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final class AccessibilityNodeInfo.CollectionInfo.Builder {
+    ctor @FlaggedApi("android.view.accessibility.collection_info_item_counts") public AccessibilityNodeInfo.CollectionInfo.Builder();
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build();
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int);
   }
 
   public static final class AccessibilityNodeInfo.CollectionItemInfo {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 32d252e..275fe77 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3868,8 +3868,9 @@
   public class PackageInstaller {
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
     field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
     field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3883,12 +3884,20 @@
     field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
     field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID";
     field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
     field public static final int LOCATION_DATA_APP = 0; // 0x0
     field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
     field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
     field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0
     field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1
     field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0
   }
 
   public static class PackageInstaller.InstallInfo {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 02eaf0b..8af1216 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -232,6 +232,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.org.conscrypt.TrustedCertificateStore;
 import com.android.server.am.MemInfoDumpProto;
+import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
 import dalvik.system.AppSpecializationHooks;
@@ -3713,7 +3714,13 @@
         final ArrayList<ResultInfo> list = new ArrayList<>();
         list.add(new ResultInfo(id, requestCode, resultCode, data));
         final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread);
-        clientTransaction.addCallback(ActivityResultItem.obtain(activityToken, list));
+        final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
+                activityToken, list);
+        if (Flags.bundleClientTransactionFlag()) {
+            clientTransaction.addTransactionItem(activityResultItem);
+        } else {
+            clientTransaction.addCallback(activityResultItem);
+        }
         try {
             mAppThread.scheduleTransaction(clientTransaction);
         } catch (RemoteException e) {
@@ -4492,16 +4499,26 @@
 
     private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
         final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
-        transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.token,
+        final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
                 r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
-                /* dontReport */ false, /* autoEnteringPip */ false));
+                /* dontReport */ false, /* autoEnteringPip */ false);
+        if (Flags.bundleClientTransactionFlag()) {
+            transaction.addTransactionItem(pauseActivityItem);
+        } else {
+            transaction.setLifecycleStateRequest(pauseActivityItem);
+        }
         executeTransaction(transaction);
     }
 
     private void scheduleResume(ActivityClientRecord r) {
         final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
-        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(r.token,
-                /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
+        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token,
+                /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+        if (Flags.bundleClientTransactionFlag()) {
+            transaction.addTransactionItem(resumeActivityItem);
+        } else {
+            transaction.setLifecycleStateRequest(resumeActivityItem);
+        }
         executeTransaction(transaction);
     }
 
@@ -6092,8 +6109,13 @@
                 TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
         // Schedule the transaction.
         final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
-        transaction.addCallback(activityRelaunchItem);
-        transaction.setLifecycleStateRequest(lifecycleRequest);
+        if (Flags.bundleClientTransactionFlag()) {
+            transaction.addTransactionItem(activityRelaunchItem);
+            transaction.addTransactionItem(lifecycleRequest);
+        } else {
+            transaction.addCallback(activityRelaunchItem);
+            transaction.setLifecycleStateRequest(lifecycleRequest);
+        }
         executeTransaction(transaction);
     }
 
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ca6d8df..a4c3bb8 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -80,6 +80,8 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.res.ApkAssets;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -144,6 +146,7 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /** @hide */
 public class ApplicationPackageManager extends PackageManager {
@@ -4024,4 +4027,34 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    @Override
+    public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+            @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
+        Objects.requireNonNull(apkFilePath, "apkFilePath cannot be null");
+        Objects.requireNonNull(parserFunction, "parserFunction cannot be null");
+        try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFilePath)) {
+            return parserFunction.apply(xmlResourceParser);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to get the android manifest parser", e);
+            throw e;
+        }
+    }
+
+    private static XmlResourceParser getAndroidManifestParser(@NonNull String apkFilePath)
+            throws IOException {
+        ApkAssets apkAssets = null;
+        try {
+            apkAssets = ApkAssets.loadFromPath(apkFilePath);
+            return apkAssets.openXml(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
+        } finally {
+            if (apkAssets != null) {
+                try {
+                    apkAssets.close();
+                } catch (Throwable ignored) {
+                    Log.w(TAG, "Failed to close apkAssets", ignored);
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 56d0d1f..6c9c14f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -51,6 +51,7 @@
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
@@ -1253,7 +1254,8 @@
      * <p>
      * If this method returns true, calls to
      * {@link #updateAutomaticZenRule(String, AutomaticZenRule)} may fail and apps should defer
-     * rule management to system settings/uis.
+     * rule management to system settings/uis via
+     * {@link Settings#ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
     public boolean areAutomaticZenRulesUserManaged() {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index cc56a1c..d8448dc 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -72,6 +72,7 @@
 per-file *Zen* = file:/packages/SystemUI/OWNERS
 per-file *StatusBar* = file:/packages/SystemUI/OWNERS
 per-file *UiModeManager* = file:/packages/SystemUI/OWNERS
+per-file notification.aconfig = file:/packages/SystemUI/OWNERS
 
 # PackageManager
 per-file ApplicationPackageManager.java = file:/services/core/java/com/android/server/pm/OWNERS
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index bbd07b8..35ce102 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -48,3 +48,10 @@
   description: "Update DumpSys to include information about migrated APIs in DPE"
   bug: "304999634"
 }
+
+flag {
+  name: "quiet_mode_credential_bug_fix"
+  namespace: "enterprise"
+  description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped."
+  bug: "293441361"
+}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 7c34cde..5e55268 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -75,7 +75,6 @@
     /**
      * Adds a message to the end of the sequence of transaction items.
      * @param item A single message that can contain a client activity/window request/callback.
-     * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}.
      */
     public void addTransactionItem(@NonNull ClientTransactionItem item) {
         if (mTransactionItems == null) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index dfbccb4..475c6fb 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -235,7 +235,9 @@
      *   Configuration - ActivityResult - Configuration - ActivityResult
      * index 1 will be returned, because ActivityResult request on position 1 will be the last
      * request that moves activity to the RESUMED state where it will eventually end.
+     * @deprecated to be removed with {@link TransactionExecutor#executeCallbacks}.
      */
+    @Deprecated
     static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
         final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
         if (callbacks == null || callbacks.isEmpty()
diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING
index 49a4467..47a152a 100644
--- a/core/java/android/app/time/TEST_MAPPING
+++ b/core/java/android/app/time/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeCoreTests",
       "options": [
@@ -8,7 +7,10 @@
           "include-filter": "android.app."
         }
       ]
-    },
+    }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
     {
       "name": "FrameworksServicesTests",
       "options": [
diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING
index c050a55..9517fb9 100644
--- a/core/java/android/app/timedetector/TEST_MAPPING
+++ b/core/java/android/app/timedetector/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeCoreTests",
       "options": [
@@ -8,7 +7,10 @@
           "include-filter": "android.app."
         }
       ]
-    },
+    }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
     {
       "name": "FrameworksServicesTests",
       "options": [
diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING
index 46656d1..fd41b86 100644
--- a/core/java/android/app/timezonedetector/TEST_MAPPING
+++ b/core/java/android/app/timezonedetector/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeCoreTests",
       "options": [
@@ -8,7 +7,10 @@
           "include-filter": "android.app."
         }
       ]
-    },
+    }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
     {
       "name": "FrameworksServicesTests",
       "options": [
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java
index 016d97f..aa32392 100644
--- a/core/java/android/app/usage/ParcelableUsageEventList.java
+++ b/core/java/android/app/usage/ParcelableUsageEventList.java
@@ -48,13 +48,16 @@
 
     private List<Event> mList;
 
-    public ParcelableUsageEventList(List<Event> list) {
+    public ParcelableUsageEventList(@NonNull List<Event> list) {
+        if (list == null) {
+            throw new IllegalArgumentException("Empty list");
+        }
         mList = list;
     }
 
     private ParcelableUsageEventList(Parcel in) {
         final int N = in.readInt();
-        mList = new ArrayList<>();
+        mList = new ArrayList<>(N);
         if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
         if (N <= 0) {
             return;
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 1eb452c..6c7eba0 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -24,9 +24,11 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -35,6 +37,7 @@
  * from which to read {@link android.app.usage.UsageEvents.Event} objects.
  */
 public final class UsageEvents implements Parcelable {
+    private static final String TAG = "UsageEvents";
 
     /** @hide */
     public static final String INSTANT_APP_PACKAGE_NAME = "android.instant_app";
@@ -786,10 +789,20 @@
     }
 
     private void readUsageEventsFromParcelWithParceledList(Parcel in) {
+        mEventCount = in.readInt();
         mIndex = in.readInt();
-        mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(),
-            ParcelableUsageEventList.class).getList();
-        mEventCount = mEventsToWrite.size();
+        ParcelableUsageEventList slice = in.readParcelable(getClass().getClassLoader(),
+                ParcelableUsageEventList.class);
+        if (slice != null) {
+            mEventsToWrite = slice.getList();
+        } else {
+            mEventsToWrite = new ArrayList<>();
+        }
+
+        if (mEventCount != mEventsToWrite.size()) {
+            Log.w(TAG, "Partial usage event list received: " + mEventCount + " != "
+                    + mEventsToWrite.size());
+        }
     }
 
     private void readUsageEventsFromParcelWithBlob(Parcel in) {
@@ -1065,6 +1078,7 @@
     }
 
     private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) {
+        dest.writeInt(mEventCount);
         dest.writeInt(mIndex);
         dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags);
     }
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 59ed045..1f25fd0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.app.PendingIntent;
 import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.IPackageInstallerCallback;
@@ -82,7 +83,7 @@
     void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
-    void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
+    void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)")
     void installPackageArchived(in ArchivedPackageParcel archivedPackageParcel,
@@ -90,4 +91,6 @@
             in IntentSender statusReceiver,
             String installerPackageName, in UserHandle userHandle);
 
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+    void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle);
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index e9a2aaa..4f0bfc7 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -387,6 +387,24 @@
             "android.content.pm.extra.UNARCHIVE_ALL_USERS";
 
     /**
+     * Current status of an unarchive operation. Will be one of
+     * {@link #UNARCHIVAL_OK}, {@link #UNARCHIVAL_ERROR_USER_ACTION_NEEDED},
+     * {@link #UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE}, {@link #UNARCHIVAL_ERROR_NO_CONNECTIVITY},
+     * {@link #UNARCHIVAL_GENERIC_ERROR}, {@link #UNARCHIVAL_ERROR_INSTALLER_DISABLED} or
+     * {@link #UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED}.
+     *
+     * <p> If the status is not {@link #UNARCHIVAL_OK}, then {@link Intent#EXTRA_INTENT} will be set
+     * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a
+     * failure dialog.
+     *
+     * @see #requestUnarchive
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
+
+    /**
      * A list of warnings that occurred during installation.
      *
      * @hide
@@ -652,6 +670,102 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserActionReason {}
 
+    /**
+     * The unarchival is possible and will commence.
+     *
+     * <p> Note that this does not mean that the unarchival has completed. This status should be
+     * sent before any longer asynchronous action (e.g. app download) is started.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_OK = 0;
+
+    /**
+     * The user needs to interact with the installer to enable the installation.
+     *
+     * <p> An example use case for this could be that the user needs to login to allow the
+     * download for a paid app.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1;
+
+    /**
+     * Not enough storage to unarchive the application.
+     *
+     * <p> The installer can optionally provide a {@code userActionIntent} for a space-clearing
+     * dialog. If no action is provided, then a generic intent
+     * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2;
+
+    /**
+     * The device is not connected to the internet
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3;
+
+    /**
+     * The installer responsible for the unarchival is disabled.
+     *
+     * <p> Should only be used by the system.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4;
+
+    /**
+     * The installer responsible for the unarchival has been uninstalled
+     *
+     * <p> Should only be used by the system.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5;
+
+    /**
+     * Generic error: The app cannot be unarchived.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_GENERIC_ERROR = 100;
+
+    /**
+     * The set of error types that can be set for
+     * {@link #reportUnarchivalStatus(int, int, PendingIntent)}.
+     *
+     * @hide
+     */
+    @IntDef(value = {
+            UNARCHIVAL_OK,
+            UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+            UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+            UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+            UNARCHIVAL_ERROR_INSTALLER_DISABLED,
+            UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED,
+            UNARCHIVAL_GENERIC_ERROR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UnarchivalStatus {}
+
+
     /** Default set of checksums - includes all available checksums.
      * @see Session#requestChecksums  */
     private static final int DEFAULT_CHECKSUMS =
@@ -2238,8 +2352,8 @@
      * Requests to archive a package which is currently installed.
      *
      * <p> During the archival process, the apps APKs and cache are removed from the device while
-     * the user data is kept. Through the {@link #requestUnarchive(String)} call, apps can be
-     * restored again through their responsible installer.
+     * the user data is kept. Through the {@link #requestUnarchive} call, apps
+     * can be restored again through their responsible installer.
      *
      * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
      * will be displayed to users with UI treatment to highlight that said apps are archived. If
@@ -2278,6 +2392,10 @@
      * <p> The installation will happen asynchronously and can be observed through
      * {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
      *
+     * @param statusReceiver Callback used to notify whether the installer has accepted the
+     *                       unarchival request or an error has occurred. The status update will be
+     *                       sent though {@link EXTRA_UNARCHIVE_STATUS}. Only one status will be
+     *                       sent.
      * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
      *                                              visible to the caller or if the package has no
      *                                              installer on the device anymore to unarchive it.
@@ -2290,10 +2408,10 @@
             Manifest.permission.REQUEST_INSTALL_PACKAGES})
     @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
-    public void requestUnarchive(@NonNull String packageName)
+    public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
             throws IOException, PackageManager.NameNotFoundException {
         try {
-            mInstaller.requestUnarchive(packageName, mInstallerPackageName,
+            mInstaller.requestUnarchive(packageName, mInstallerPackageName, statusReceiver,
                     new UserHandle(mUserId));
         } catch (ParcelableException e) {
             e.maybeRethrow(IOException.class);
@@ -2303,6 +2421,39 @@
         }
     }
 
+    /**
+     * Reports the status of an unarchival to the system.
+     *
+     * @param unarchiveId          the ID provided by the system as part of the
+     *                             intent.action.UNARCHIVE broadcast with EXTRA_UNARCHIVE_ID.
+     * @param status               is used for the system to provide the user with necessary
+     *                             follow-up steps or errors.
+     * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field
+     *                             should be set to specify how many additional bytes of storage
+     *                             are required to unarchive the app.
+     * @param userActionIntent     Optional intent to start a follow up action required to
+     *                             facilitate the unarchival flow (e.g. user needs to log in).
+     * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.INSTALL_PACKAGES,
+            Manifest.permission.REQUEST_INSTALL_PACKAGES})
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status,
+            long requiredStorageBytes, @Nullable PendingIntent userActionIntent)
+            throws PackageManager.NameNotFoundException {
+        try {
+            mInstaller.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes,
+                    userActionIntent, new UserHandle(mUserId));
+        } catch (ParcelableException e) {
+            e.maybeRethrow(PackageManager.NameNotFoundException.class);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     // (b/239722738) This class serves as a bridge between the PackageLite class, which
     // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
     // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
@@ -2566,6 +2717,8 @@
         public int developmentInstallFlags = 0;
         /** {@hide} */
         public int unarchiveId = -1;
+        /** {@hide} */
+        public IntentSender unarchiveIntentSender;
 
         private final ArrayMap<String, Integer> mPermissionStates;
 
@@ -2618,6 +2771,7 @@
             applicationEnabledSettingPersistent = source.readBoolean();
             developmentInstallFlags = source.readInt();
             unarchiveId = source.readInt();
+            unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
         }
 
         /** {@hide} */
@@ -2652,6 +2806,7 @@
             ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
             ret.developmentInstallFlags = developmentInstallFlags;
             ret.unarchiveId = unarchiveId;
+            ret.unarchiveIntentSender = unarchiveIntentSender;
             return ret;
         }
 
@@ -3364,6 +3519,7 @@
                     applicationEnabledSettingPersistent);
             pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
             pw.printPair("unarchiveId", unarchiveId);
+            pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
             pw.println();
         }
 
@@ -3408,6 +3564,7 @@
             dest.writeBoolean(applicationEnabledSettingPersistent);
             dest.writeInt(developmentInstallFlags);
             dest.writeInt(unarchiveId);
+            dest.writeParcelable(unarchiveIntentSender, flags);
         }
 
         public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c3b3423..fe31c9d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -34,6 +34,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
 import android.annotation.XmlRes;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
@@ -96,6 +97,7 @@
 import dalvik.system.VMRuntime;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.cert.Certificate;
@@ -108,6 +110,7 @@
 import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Class for retrieving various kinds of information related to the application
@@ -11426,4 +11429,60 @@
         throw new UnsupportedOperationException(
                 "unregisterPackageMonitorCallback not implemented in subclass");
     }
+
+    /**
+     * Retrieve AndroidManifest.xml information for the given application apk path.
+     *
+     * <p>Example:
+     *
+     * <pre><code>
+     * Bundle result;
+     * try {
+     *     result = getContext().getPackageManager().parseAndroidManifest(apkFilePath,
+     *             xmlResourceParser -> {
+     *                 Bundle bundle = new Bundle();
+     *                 // Search the start tag
+     *                 int type;
+     *                 while ((type = xmlResourceParser.next()) != XmlPullParser.START_TAG
+     *                         &amp;&amp; type != XmlPullParser.END_DOCUMENT) {
+     *                 }
+     *                 if (type != XmlPullParser.START_TAG) {
+     *                     return bundle;
+     *                 }
+     *
+     *                 // Start to read the tags and attributes from the xmlResourceParser
+     *                 if (!xmlResourceParser.getName().equals("manifest")) {
+     *                     return bundle;
+     *                 }
+     *                 String packageName = xmlResourceParser.getAttributeValue(null, "package");
+     *                 bundle.putString("package", packageName);
+     *
+     *                 // Continue to read the tags and attributes from the xmlResourceParser
+     *
+     *                 return bundle;
+     *             });
+     * } catch (IOException e) {
+     * }
+     * </code></pre>
+     *
+     * Note: When the parserFunction is invoked, the client can read the AndroidManifest.xml
+     * information by the XmlResourceParser object. After leaving the parserFunction, the
+     * XmlResourceParser object will be closed.
+     *
+     * @param apkFilePath The path of an application apk file.
+     * @param parserFunction The parserFunction will be invoked with the XmlResourceParser object
+     *        after getting the AndroidManifest.xml of an application package.
+     *
+     * @return Returns the result of the {@link Function#apply(Object)}.
+     *
+     * @throws IOException if the AndroidManifest.xml of an application package cannot be
+     *             read or accessed.
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_GET_PACKAGE_INFO)
+    @WorkerThread
+    public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+            @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
+        throw new UnsupportedOperationException(
+                "parseAndroidManifest not implemented in subclass");
+    }
 }
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index f817fb8..1100731 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -82,8 +82,8 @@
         private static final int MAIN_INDEX_SIZE = 1 <<  LOG_MAIN_INDEX_SIZE;
         private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
         /**
-         * Debuggable builds will throw an BinderProxyMapSizeException if the number of
-         * map entries exceeds:
+         * We will throw a BinderProxyMapSizeException if the number of map entries
+         * exceeds:
          */
         private static final int CRASH_AT_SIZE = 25_000;
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 33c15d77..ff6ec29 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -36,6 +37,7 @@
 import android.app.AppOpsManager;
 import android.app.Application;
 import android.app.AutomaticZenRule;
+import android.app.Flags;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.SearchManager;
@@ -1904,6 +1906,36 @@
             = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
 
     /**
+     * Activity Action: Shows the settings page for an {@link AutomaticZenRule} mode.
+     * <p>
+     * Users can change the behavior of the mode when it's activated and access the owning app's
+     * additional configuration screen, where triggering criteria can be modified (see
+     * {@link AutomaticZenRule#setConfigurationActivity(ComponentName)}).
+     * <p>
+     * A matching Activity will only be found if
+     * {@link NotificationManager#areAutomaticZenRulesUserManaged()} is true.
+     * <p>
+     * Input: Intent's data URI set with an application name, using the "package" schema (like
+     * "package:com.my.app").
+     * Input: The id of the rule, provided in {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID}.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
+            = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
+
+    /**
+     * Activity Extra: The String id of the {@link AutomaticZenRule mode} settings to display.
+     * <p>
+     * This must be passed as an extra field to the {@link #ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID
+            = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
+
+    /**
      * Activity Action: Show settings for video captioning.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard
diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING
index 21a8eab..e5910ea 100644
--- a/core/java/android/service/timezone/TEST_MAPPING
+++ b/core/java/android/service/timezone/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeCoreTests",
       "options": [
@@ -8,7 +7,10 @@
           "include-filter": "android.service."
         }
       ]
-    },
+    }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
     {
       "name": "CtsLocationTimeZoneManagerHostTest"
     }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 43bfe13..53aed49 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -23,7 +23,7 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.Hide;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -752,6 +752,7 @@
      *     {@link #isGranularScrollingSupported()} to check if granular scrolling is supported.
      * </p>
      */
+    @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
     public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT =
             "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
 
@@ -2608,6 +2609,7 @@
      * @return True if all scroll actions that could support
      * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT} have done so, false otherwise.
      */
+    @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
     public boolean isGranularScrollingSupported() {
         return getBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING);
     }
@@ -2626,6 +2628,7 @@
      *
      * @throws IllegalStateException If called from an AccessibilityService.
      */
+    @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
     public void setGranularScrollingSupported(boolean granularScrollingSupported) {
         setBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING,
                 granularScrollingSupported);
@@ -6119,6 +6122,7 @@
          * This should be used for {@code mItemCount} and
          * {@code mImportantForAccessibilityItemCount} when values for those fields are not known.
          */
+        @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
         public static final int UNDEFINED = -1;
 
         private int mRowCount;
@@ -6229,8 +6233,8 @@
          *                  the item count is not known.
          * @param importantForAccessibilityItemCount The count of the collection's views considered
          *                                           important for accessibility.
+         * @hide
          */
-        @Hide
         public CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
                 int selectionMode, int itemCount, int importantForAccessibilityItemCount) {
             mRowCount = rowCount;
@@ -6287,6 +6291,7 @@
          *
          * @return The count of items, which may be {@code UNDEFINED} if the count is not known.
          */
+        @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
         public int getItemCount() {
             return mItemCount;
         }
@@ -6297,6 +6302,7 @@
          * @return The count of items important for accessibility, which may be {@code UNDEFINED}
          * if the count is not known.
          */
+        @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
         public int getImportantForAccessibilityItemCount() {
             return mImportantForAccessibilityItemCount;
         }
@@ -6323,6 +6329,7 @@
          * The builder for CollectionInfo.
          */
 
+        @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
         public static final class Builder {
             private int mRowCount = 0;
             private int mColumnCount = 0;
@@ -6334,6 +6341,7 @@
             /**
              * Creates a new Builder.
              */
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public Builder() {
             }
 
@@ -6343,6 +6351,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setRowCount(int rowCount) {
                 mRowCount = rowCount;
                 return this;
@@ -6354,6 +6363,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setColumnCount(int columnCount) {
                 mColumnCount = columnCount;
                 return this;
@@ -6364,6 +6374,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setHierarchical(boolean hierarchical) {
                 mHierarchical = hierarchical;
                 return this;
@@ -6375,6 +6386,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setSelectionMode(int selectionMode) {
                 mSelectionMode = selectionMode;
                 return this;
@@ -6389,6 +6401,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setItemCount(int itemCount) {
                 mItemCount = itemCount;
                 return this;
@@ -6401,6 +6414,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setImportantForAccessibilityItemCount(
                     int importantForAccessibilityItemCount) {
                 mImportantForAccessibilityItemCount = importantForAccessibilityItemCount;
@@ -6411,6 +6425,7 @@
              * Creates a new {@link CollectionInfo} instance.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo build() {
                 CollectionInfo collectionInfo = new CollectionInfo(mRowCount, mColumnCount,
                         mHierarchical);
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 950fa4b..c337cb4 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -17,6 +17,13 @@
 }
 
 flag {
+    namespace: "accessibility"
+    name: "collection_info_item_counts"
+    description: "Fields for total items and the number of important for accessibility items in a collection"
+    bug: "302376158"
+}
+
+flag {
     name: "deduplicate_accessibility_warning_dialog"
     namespace: "accessibility"
     description: "Removes duplicate definition of the accessibility warning dialog."
@@ -39,6 +46,13 @@
 
 flag {
     namespace: "accessibility"
+    name: "granular_scrolling"
+    description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
+    bug: "302376158"
+}
+
+flag {
+    namespace: "accessibility"
     name: "update_always_on_a11y_service"
     description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
     bug: "298869916"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index b600b22..933cc49 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -9,6 +9,16 @@
     bug: "260873529"
 }
 
+# Using a fixed read only flag because there are ClientTransaction scheduling before
+# WindowManagerService creation.
+flag {
+    namespace: "windowing_sdk"
+    name: "bundle_client_transaction_flag"
+    description: "To bundle multiple ClientTransactionItems into one ClientTransaction"
+    bug: "260873529"
+    is_fixed_read_only: true
+}
+
 flag {
     namespace: "windowing_sdk"
     name: "activity_embedding_overlay_presentation_flag"
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index e3bb1fe..7be27be 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -54,18 +54,6 @@
      */
     public static final class NotificationFlags {
 
-        /**
-         * FOR DEVELOPMENT / TESTING ONLY!!!
-         * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
-         * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI
-         */
-        public static final Flag FSI_FORCE_DEMOTE =
-                devFlag("persist.sysui.notification.fsi_force_demote");
-
-        /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
-        public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
-                releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
-
         /** Gating the logging of DND state change events. */
         public static final Flag LOG_DND_STATE_EVENTS =
                 releasedFlag("persist.sysui.notification.log_dnd_state_events");
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 5c1d91f..5b68e8e 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -184,11 +184,11 @@
 void NativeInputEventReceiver::setFdEvents(int events) {
     if (mFdEvents != events) {
         mFdEvents = events;
-        int fd = mInputConsumer.getChannel()->getFd();
+        auto&& fd = mInputConsumer.getChannel()->getFd();
         if (events) {
-            mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
+            mMessageQueue->getLooper()->addFd(fd.get(), 0, events, this, nullptr);
         } else {
-            mMessageQueue->getLooper()->removeFd(fd);
+            mMessageQueue->getLooper()->removeFd(fd.get());
         }
     }
 }
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 833952d..6bdf821 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -102,8 +102,8 @@
 }
 
 status_t NativeInputEventSender::initialize() {
-    int receiveFd = mInputPublisher.getChannel()->getFd();
-    mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
+    auto&& receiveFd = mInputPublisher.getChannel()->getFd();
+    mMessageQueue->getLooper()->addFd(receiveFd.get(), 0, ALOOPER_EVENT_INPUT, this, NULL);
     return OK;
 }
 
@@ -112,7 +112,7 @@
         LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
     }
 
-    mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
+    mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd().get());
 }
 
 status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4b02257..2327b20 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -42,6 +42,7 @@
 import android.app.PictureInPictureParams;
 import android.app.ResourcesManager;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
+import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
@@ -73,6 +74,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.content.ReferrerIntent;
+import com.android.window.flags.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -227,7 +229,8 @@
         try {
             // Send process level config change.
             ClientTransaction transaction = newTransaction(activityThread);
-            transaction.addCallback(ConfigurationChangeItem.obtain(newConfig, DEVICE_ID_INVALID));
+            addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+                    newConfig, DEVICE_ID_INVALID));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
@@ -243,7 +246,7 @@
             newConfig.seq++;
             newConfig.smallestScreenWidthDp++;
             transaction = newTransaction(activityThread);
-            transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+            addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
                     activity.getActivityToken(), newConfig));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -444,16 +447,16 @@
         activity.mTestLatch = new CountDownLatch(1);
 
         ClientTransaction transaction = newTransaction(activityThread);
-        transaction.addCallback(ConfigurationChangeItem.obtain(
+        addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
                 processConfigLandscape, DEVICE_ID_INVALID));
         appThread.scheduleTransaction(transaction);
 
         transaction = newTransaction(activityThread);
-        transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+        addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
                 activity.getActivityToken(), activityConfigLandscape));
-        transaction.addCallback(ConfigurationChangeItem.obtain(
+        addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
                 processConfigPortrait, DEVICE_ID_INVALID));
-        transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+        addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
                 activity.getActivityToken(), activityConfigPortrait));
         appThread.scheduleTransaction(transaction);
 
@@ -840,8 +843,8 @@
                         false /* shouldSendCompatFakeFocus*/);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.addCallback(callbackItem);
-        transaction.setLifecycleStateRequest(resumeStateRequest);
+        addClientTransactionItem(transaction, callbackItem);
+        addClientTransactionItem(transaction, resumeStateRequest);
 
         return transaction;
     }
@@ -853,7 +856,7 @@
                         false /* shouldSendCompatFakeFocus */);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.setLifecycleStateRequest(resumeStateRequest);
+        addClientTransactionItem(transaction, resumeStateRequest);
 
         return transaction;
     }
@@ -864,7 +867,7 @@
                 activity.getActivityToken(), 0 /* configChanges */);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.setLifecycleStateRequest(stopStateRequest);
+        addClientTransactionItem(transaction, stopStateRequest);
 
         return transaction;
     }
@@ -876,7 +879,7 @@
                 activity.getActivityToken(), config);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.addCallback(item);
+        addClientTransactionItem(transaction, item);
 
         return transaction;
     }
@@ -888,7 +891,7 @@
                 resume);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.addCallback(item);
+        addClientTransactionItem(transaction, item);
 
         return transaction;
     }
@@ -903,6 +906,17 @@
         return ClientTransaction.obtain(activityThread.getApplicationThread());
     }
 
+    private static void addClientTransactionItem(@NonNull ClientTransaction transaction,
+            @NonNull ClientTransactionItem item) {
+        if (Flags.bundleClientTransactionFlag()) {
+            transaction.addTransactionItem(item);
+        } else if (item.isActivityLifecycleItem()) {
+            transaction.setLifecycleStateRequest((ActivityLifecycleItem) item);
+        } else {
+            transaction.addCallback(item);
+        }
+    }
+
     // Test activity
     public static class TestActivity extends Activity {
         static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
index 2ec58d4..5a202c5 100644
--- a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
+++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -43,11 +44,29 @@
 @LargeTest
 public class ParcelableUsageEventListTest {
     private static final int SMALL_TEST_EVENT_COUNT = 100;
-    private static final int LARGE_TEST_EVENT_COUNT = 10000;
+    private static final int LARGE_TEST_EVENT_COUNT = 30000;
 
     private Random mRandom = new Random();
 
     @Test
+    public void testNullList() throws Exception {
+        Parcel parcel = Parcel.obtain();
+        try {
+            parcel.writeParcelable(new ParcelableUsageEventList(null), 0);
+            fail("Expected IllegalArgumentException with null list.");
+        } catch (IllegalArgumentException expected) {
+            // Expected.
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testEmptyList() throws Exception {
+        testParcelableUsageEventList(0);
+    }
+
+    @Test
     public void testSmallList() throws Exception {
         testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT);
     }
@@ -58,15 +77,15 @@
     }
 
     private void testParcelableUsageEventList(int eventCount) throws Exception {
-        List<Event> smallList = new ArrayList<>();
+        List<Event> eventList = new ArrayList<>();
         for (int i = 0; i < eventCount; i++) {
-            smallList.add(generateUsageEvent());
+            eventList.add(generateUsageEvent());
         }
 
         ParcelableUsageEventList slice;
         Parcel parcel = Parcel.obtain();
         try {
-            parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0);
+            parcel.writeParcelable(new ParcelableUsageEventList(eventList), 0);
             parcel.setDataPosition(0);
             slice = parcel.readParcelable(getClass().getClassLoader(),
                     ParcelableUsageEventList.class);
@@ -79,7 +98,7 @@
         assertEquals(eventCount, slice.getList().size());
 
         for (int i = 0; i < eventCount; i++) {
-            compareUsageEvent(smallList.get(i), slice.getList().get(i));
+            compareUsageEvent(eventList.get(i), slice.getList().get(i));
         }
     }
 
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 4533db6..3abdb6f 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -37,17 +37,17 @@
         RequestInfo::class.java
     )
 
-val Intent.getCredentialProviderDataList: List<ProviderData>
+val Intent.getCredentialProviderDataList: List<GetCredentialProviderData>
     get() = this.extras?.getParcelableArrayList(
         ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
         GetCredentialProviderData::class.java
-    ) ?: emptyList()
+    ) ?.filterIsInstance<GetCredentialProviderData>() ?: emptyList()
 
-val Intent.createCredentialProviderDataList: List<ProviderData>
+val Intent.createCredentialProviderDataList: List<CreateCredentialProviderData>
     get() = this.extras?.getParcelableArrayList(
         ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
         CreateCredentialProviderData::class.java
-    ) ?: emptyList()
+    ) ?.filterIsInstance<CreateCredentialProviderData>() ?: emptyList()
 
 val Intent.resultReceiver: ResultReceiver?
     get() = this.getParcelableExtra(
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index ee45fbb..d4bca2a 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -18,7 +18,6 @@
 
 import android.content.Intent
 import android.credentials.ui.Entry
-import android.credentials.ui.GetCredentialProviderData
 import androidx.credentials.provider.PasswordCredentialEntry
 import com.android.credentialmanager.factory.fromSlice
 import com.android.credentialmanager.ktx.getCredentialProviderDataList
@@ -32,12 +31,10 @@
 fun Intent.toGet(): Request.Get {
     val credentialEntries = mutableListOf<Pair<String, Entry>>()
     for (providerData in getCredentialProviderDataList) {
-        if (providerData is GetCredentialProviderData) {
-            for (credentialEntry in providerData.credentialEntries) {
-                credentialEntries.add(
-                    Pair(providerData.providerFlattenedComponentName, credentialEntry)
-                )
-            }
+        for (credentialEntry in providerData.credentialEntries) {
+            credentialEntries.add(
+                Pair(providerData.providerFlattenedComponentName, credentialEntry)
+            )
         }
     }
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2c15fc6..8412cba 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -249,6 +249,8 @@
         Settings.Secure.HUB_MODE_TUTORIAL_STATE,
         Settings.Secure.STYLUS_BUTTONS_ENABLED,
         Settings.Secure.STYLUS_HANDWRITING_ENABLED,
-        Settings.Secure.DEFAULT_NOTE_TASK_PROFILE
+        Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+        Settings.Secure.CREDENTIAL_SERVICE,
+        Settings.Secure.CREDENTIAL_SERVICE_PRIMARY
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 71c2ddc..9197554 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -19,11 +19,13 @@
 import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR;
@@ -62,7 +64,6 @@
         VALIDATORS.put(Secure.ADAPTIVE_CHARGING_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.CAMERA_AUTOROTATE, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Secure.AUTOFILL_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR);
         VALIDATORS.put(
                 Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
                 new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE));
@@ -398,5 +399,8 @@
         VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED,
                 new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
         VALIDATORS.put(Secure.DEFAULT_NOTE_TASK_PROFILE, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.CREDENTIAL_SERVICE, CREDENTIAL_SERVICE_VALIDATOR);
+        VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR);
+        VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index 49012b0..a8a659e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -235,4 +235,30 @@
             }
         }
     };
+
+    static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(String value) {
+            if (value == null || value.equals("")) {
+                return true;
+            }
+
+            return COLON_SEPARATED_COMPONENT_LIST_VALIDATOR.validate(value);
+        }
+    };
+
+    static final Validator AUTOFILL_SERVICE_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(String value) {
+            if (value == null || value.equals("")) {
+                return true;
+            }
+
+            if (value.equals("credential-provider")) {
+               return true;
+            }
+
+            return NULLABLE_COMPONENT_NAME_VALIDATOR.validate(value);
+        }
+    };
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f1b53ed..efed8c3 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -851,8 +851,6 @@
                  Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
                  Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
                  Settings.Secure.UI_TRANSLATION_ENABLED,
-                 Settings.Secure.CREDENTIAL_SERVICE,
-                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                  Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
                  Settings.Secure.DND_CONFIGS_MIGRATED,
                  Settings.Secure.NAVIGATION_MODE_RESTORE);
diff --git a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
index 865f431..3b3bf8ca 100644
--- a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
@@ -340,6 +340,60 @@
         failIfOffendersPresent(offenders, "Settings.Secure");
     }
 
+    @Test
+    public void testCredentialServiceValidator_returnsTrueIfNull() {
+        assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testCredentialServiceValidator_returnsTrueIfEmpty() {
+        assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(""));
+    }
+
+    @Test
+    public void testCredentialServiceValidator_returnsTrueIfSingleComponentName() {
+        assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(
+            "android.credentials/android.credentials.Test"));
+    }
+
+    @Test
+    public void testCredentialServiceValidator_returnsTrueIfMultipleComponentName() {
+        assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(
+            "android.credentials/android.credentials.Test"
+            + ":android.credentials/.Test2"));
+    }
+
+    @Test
+    public void testCredentialServiceValidator_returnsFalseIfInvalidComponentName() {
+        assertFalse(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate("test"));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsTrueIfNull() {
+        assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsTrueIfEmpty() {
+        assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(""));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsTrueIfPlaceholder() {
+        assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("credential-provider"));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsTrueIfSingleComponentName() {
+        assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(
+            "android.credentials/android.credentials.Test"));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsFalseIfInvalidComponentName() {
+        assertFalse(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("test"));
+    }
+
     private void failIfOffendersPresent(String offenders, String settingsType) {
         if (offenders.length() > 0) {
             fail("All " + settingsType + " settings that are backed up have to have a non-null"
diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp
deleted file mode 100644
index 88dad66..0000000
--- a/packages/SystemUI/communal/layout/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2023 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.
-
-package {
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
-    name: "CommunalLayoutLib",
-    srcs: [
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "androidx.arch.core_core-runtime",
-        "androidx.compose.animation_animation-graphics",
-        "androidx.compose.runtime_runtime",
-        "androidx.compose.material3_material3",
-        "jsr330",
-        "kotlinx-coroutines-android",
-        "kotlinx-coroutines-core",
-    ],
-    manifest: "AndroidManifest.xml",
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/packages/SystemUI/communal/layout/AndroidManifest.xml
deleted file mode 100644
index 141be07..0000000
--- a/packages/SystemUI/communal/layout/AndroidManifest.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2023 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.
--->
-
-<manifest package="com.android.systemui.communal.layout" />
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
deleted file mode 100644
index 91fe33c..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 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.
- */
-
-package com.android.systemui.communal.layout
-
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-
-/** Computes the arrangement of cards. */
-class CommunalLayoutEngine {
-    companion object {
-        /**
-         * Determines the size that each card should be rendered in, and distributes the cards into
-         * columns.
-         *
-         * Returns a nested list where the outer list contains columns, and the inner list contains
-         * cards in each column.
-         *
-         * Currently treats the first supported size as the size to be rendered in, ignoring other
-         * supported sizes.
-         *
-         * Cards are ordered by priority, from highest to lowest.
-         */
-        fun distributeCardsIntoColumns(
-            cards: List<CommunalGridLayoutCard>,
-        ): List<List<CommunalGridLayoutCardInfo>> {
-            val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>()
-
-            var capacityOfLastColumn = 0
-            val sorted = cards.sortedByDescending { it.priority }
-            for (card in sorted) {
-                val cardSize = card.supportedSizes.first()
-                if (capacityOfLastColumn >= cardSize.value) {
-                    // Card fits in last column
-                    capacityOfLastColumn -= cardSize.value
-                } else {
-                    // Create a new column
-                    result.add(arrayListOf())
-                    capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value
-                }
-
-                result.last().add(CommunalGridLayoutCardInfo(card, cardSize))
-            }
-
-            return result
-        }
-    }
-
-    /**
-     * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the
-     * card should be rendered in.
-     */
-    data class CommunalGridLayoutCardInfo(
-        val card: CommunalGridLayoutCard,
-        val size: CommunalGridLayoutCard.Size,
-    )
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
deleted file mode 100644
index 33024f7..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 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.
- */
-
-package com.android.systemui.communal.layout.ui.compose
-
-import android.util.SizeF
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.systemui.communal.layout.CommunalLayoutEngine
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
-
-/**
- * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size
- * and follows a specific order based on its priority, ensuring a seamless layout without any gaps.
- */
-@Composable
-fun CommunalGridLayout(
-    modifier: Modifier,
-    layoutConfig: CommunalGridLayoutConfig,
-    communalCards: List<CommunalGridLayoutCard>,
-) {
-    val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards)
-    LazyRow(
-        modifier = modifier.height(layoutConfig.gridHeight),
-        horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
-    ) {
-        for (column in columns) {
-            item {
-                Column(
-                    modifier = Modifier.width(layoutConfig.cardWidth),
-                    verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
-                ) {
-                    for (cardInfo in column) {
-                        Row(
-                            modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
-                        ) {
-                            cardInfo.card.Content(
-                                modifier = Modifier.fillMaxSize(),
-                                size =
-                                    SizeF(
-                                        layoutConfig.cardWidth.value,
-                                        layoutConfig.cardHeight(cardInfo.size).value,
-                                    ),
-                            )
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
deleted file mode 100644
index 4b2a156..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 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.
- */
-
-package com.android.systemui.communal.layout.ui.compose.config
-
-import android.util.SizeF
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-
-/** A card that hosts content to be rendered in the communal grid layout. */
-abstract class CommunalGridLayoutCard {
-    /**
-     * Content to be hosted by the card.
-     *
-     * To host non-Compose views, see
-     * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
-     *
-     * @param size The size given to the card. Content of the card should fill all this space, given
-     *   that margins and paddings have been taken care of by the layout.
-     */
-    @Composable abstract fun Content(modifier: Modifier, size: SizeF)
-
-    /**
-     * Sizes supported by the card.
-     *
-     * If multiple sizes are available, they should be ranked in order of preference, from most to
-     * least preferred.
-     */
-    abstract val supportedSizes: List<Size>
-
-    /**
-     * Priority of the content hosted by the card.
-     *
-     * The value of priority is relative to other cards. Cards with a higher priority are generally
-     * ordered first.
-     */
-    open val priority: Int = 0
-
-    /**
-     * Size of the card.
-     *
-     * @param value A numeric value that represents the size. Must be less than or equal to
-     *   [Size.FULL].
-     */
-    enum class Size(val value: Int) {
-        /** The card takes up full height of the grid layout. */
-        FULL(value = 6),
-
-        /** The card takes up half of the vertical space of the grid layout. */
-        HALF(value = 3),
-
-        /** The card takes up a third of the vertical space of the grid layout. */
-        THIRD(value = 2),
-    }
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
deleted file mode 100644
index 143df83..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2023 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.
- */
-
-package com.android.systemui.communal.layout.ui.compose.config
-
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.times
-
-/**
- * Configurations of the communal grid layout.
- *
- * The communal grid layout follows Material Design's responsive layout grid (see
- * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided
- * up by columns and gutters, and each card occupies one or multiple columns.
- */
-data class CommunalGridLayoutConfig(
-    /**
-     * Size in dp of each grid column.
-     *
-     * Every card occupies one or more grid columns, which means that the width of each card is
-     * influenced by the size of the grid columns.
-     */
-    val gridColumnSize: Dp,
-
-    /**
-     * Size in dp of each grid gutter.
-     *
-     * A gutter is the space between columns that helps separate content. This is, therefore, also
-     * the size of the gaps between cards, both horizontally and vertically.
-     */
-    val gridGutter: Dp,
-
-    /**
-     * Height in dp of the grid layout.
-     *
-     * Cards with a full size take up the entire height of the grid layout.
-     */
-    val gridHeight: Dp,
-
-    /**
-     * Number of grid columns that each card occupies.
-     *
-     * It's important to note that all the cards take up the same number of grid columns, or in
-     * simpler terms, they all have the same width.
-     */
-    val gridColumnsPerCard: Int,
-) {
-    /**
-     * Width in dp of each card.
-     *
-     * It's important to note that all the cards take up the same number of grid columns, or in
-     * simpler terms, they all have the same width.
-     */
-    val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1)
-
-    /** Returns the height of a card in dp, based on its size. */
-    fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp {
-        return when (cardSize) {
-            CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1)
-            CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2)
-            CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3)
-        }
-    }
-
-    /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */
-    private fun cardHeightBy(denominator: Int): Dp {
-        return (gridHeight - (denominator - 1) * gridGutter) / denominator
-    }
-}
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
deleted file mode 100644
index 9a05504..0000000
--- a/packages/SystemUI/communal/layout/tests/Android.bp
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2023 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.
-
-package {
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_test {
-    name: "CommunalLayoutLibTests",
-    srcs: [
-        "**/*.kt",
-    ],
-    static_libs: [
-        "CommunalLayoutLib",
-        "androidx.test.runner",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "frameworks-base-testutils",
-        "junit",
-        "kotlinx_coroutines_test",
-        "mockito-target-extended-minus-junit4",
-        "platform-test-annotations",
-        "testables",
-        "truth",
-    ],
-    libs: [
-        "android.test.mock",
-        "android.test.base",
-        "android.test.runner",
-    ],
-    jni_libs: [
-        "libdexmakerjvmtiagent",
-        "libstaticjvmtiagent",
-    ],
-    manifest: "AndroidManifest.xml",
-}
diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
deleted file mode 100644
index b19007c..0000000
--- a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.communal.layout.tests">
-
-    <application android:debuggable="true" android:largeHeap="true">
-        <uses-library android:name="android.test.mock" />
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.testing.TestableInstrumentation"
-        android:targetPackage="com.android.systemui.communal.layout.tests"
-        android:label="Tests for CommunalLayoutLib">
-    </instrumentation>
-
-</manifest>
diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml
deleted file mode 100644
index 1352b23..0000000
--- a/packages/SystemUI/communal/layout/tests/AndroidTest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration description="Runs tests for CommunalLayoutLib">
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="install-arg" value="-t" />
-        <option name="test-file-name" value="CommunalLayoutLibTests.apk" />
-    </target_preparer>
-
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-suite-tag" value="framework-base-presubmit" />
-    <option name="test-tag" value="CommunalLayoutLibTests" />
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.systemui.communal.layout.tests" />
-        <option name="runner" value="android.testing.TestableInstrumentation" />
-        <option name="hidden-api-checks" value="false"/>
-    </test>
-
-</configuration>
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
deleted file mode 100644
index 50b7c5f..0000000
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-package com.android.systemui.communal.layout
-
-import android.util.SizeF
-import androidx.compose.material3.Card
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalLayoutEngineTest {
-    @Test
-    fun distribution_fullLayout() {
-        val cards =
-            listOf(
-                generateCard(CommunalGridLayoutCard.Size.FULL),
-                generateCard(CommunalGridLayoutCard.Size.HALF),
-                generateCard(CommunalGridLayoutCard.Size.HALF),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-            )
-        val expected =
-            listOf(
-                listOf(
-                    CommunalGridLayoutCard.Size.FULL,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.HALF,
-                    CommunalGridLayoutCard.Size.HALF,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.THIRD,
-                    CommunalGridLayoutCard.Size.THIRD,
-                    CommunalGridLayoutCard.Size.THIRD,
-                ),
-            )
-
-        assertDistributionBySize(cards, expected)
-    }
-
-    @Test
-    fun distribution_layoutWithGaps() {
-        val cards =
-            listOf(
-                generateCard(CommunalGridLayoutCard.Size.HALF),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-                generateCard(CommunalGridLayoutCard.Size.HALF),
-                generateCard(CommunalGridLayoutCard.Size.FULL),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-            )
-        val expected =
-            listOf(
-                listOf(
-                    CommunalGridLayoutCard.Size.HALF,
-                    CommunalGridLayoutCard.Size.THIRD,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.HALF,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.FULL,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.THIRD,
-                ),
-            )
-
-        assertDistributionBySize(cards, expected)
-    }
-
-    @Test
-    fun distribution_sortByPriority() {
-        val cards =
-            listOf(
-                generateCard(priority = 2),
-                generateCard(priority = 7),
-                generateCard(priority = 10),
-                generateCard(priority = 1),
-                generateCard(priority = 5),
-            )
-        val expected =
-            listOf(
-                listOf(10, 7),
-                listOf(5, 2),
-                listOf(1),
-            )
-
-        assertDistributionByPriority(cards, expected)
-    }
-
-    private fun assertDistributionBySize(
-        cards: List<CommunalGridLayoutCard>,
-        expected: List<List<CommunalGridLayoutCard.Size>>,
-    ) {
-        val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
-
-        for (c in expected.indices) {
-            for (r in expected[c].indices) {
-                assertThat(result[c][r].size).isEqualTo(expected[c][r])
-            }
-        }
-    }
-
-    private fun assertDistributionByPriority(
-        cards: List<CommunalGridLayoutCard>,
-        expected: List<List<Int>>,
-    ) {
-        val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
-
-        for (c in expected.indices) {
-            for (r in expected[c].indices) {
-                assertThat(result[c][r].card.priority).isEqualTo(expected[c][r])
-            }
-        }
-    }
-
-    private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
-        return object : CommunalGridLayoutCard() {
-            override val supportedSizes = listOf(size)
-
-            @Composable
-            override fun Content(modifier: Modifier, size: SizeF) {
-                Card(modifier = modifier, content = {})
-            }
-        }
-    }
-
-    private fun generateCard(priority: Int): CommunalGridLayoutCard {
-        return object : CommunalGridLayoutCard() {
-            override val supportedSizes = listOf(Size.HALF)
-            override val priority = priority
-
-            @Composable
-            override fun Content(modifier: Modifier, size: SizeF) {
-                Card(modifier = modifier, content = {})
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
deleted file mode 100644
index 946eeec..0000000
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.android.systemui.communal.layout.ui.compose.config
-
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalGridLayoutConfigTest {
-    @Test
-    fun cardWidth() {
-        Truth.assertThat(
-                CommunalGridLayoutConfig(
-                        gridColumnSize = 5.dp,
-                        gridGutter = 3.dp,
-                        gridHeight = 17.dp,
-                        gridColumnsPerCard = 1,
-                    )
-                    .cardWidth
-            )
-            .isEqualTo(5.dp)
-
-        Truth.assertThat(
-                CommunalGridLayoutConfig(
-                        gridColumnSize = 5.dp,
-                        gridGutter = 3.dp,
-                        gridHeight = 17.dp,
-                        gridColumnsPerCard = 2,
-                    )
-                    .cardWidth
-            )
-            .isEqualTo(13.dp)
-
-        Truth.assertThat(
-                CommunalGridLayoutConfig(
-                        gridColumnSize = 5.dp,
-                        gridGutter = 3.dp,
-                        gridHeight = 17.dp,
-                        gridColumnsPerCard = 3,
-                    )
-                    .cardWidth
-            )
-            .isEqualTo(21.dp)
-    }
-
-    @Test
-    fun cardHeight() {
-        val config =
-            CommunalGridLayoutConfig(
-                gridColumnSize = 5.dp,
-                gridGutter = 2.dp,
-                gridHeight = 10.dp,
-                gridColumnsPerCard = 3,
-            )
-
-        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp)
-        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp)
-        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp)
-    }
-}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 16c2437..796abf4b 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -31,7 +31,6 @@
     ],
 
     static_libs: [
-        "CommunalLayoutLib",
         "SystemUI-core",
         "PlatformComposeCore",
         "PlatformComposeSceneTransitionLayout",
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 1fa55f5..54cb501 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -384,6 +384,11 @@
         }
     }
 
+    @Nullable
+    View getAodNotifIconContainer() {
+        return mAodIconContainer;
+    }
+
     @Override
     protected void onViewDetached() {
         mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
@@ -639,6 +644,7 @@
                 }
             } else {
                 mNotificationIconAreaController.setupAodIcons(nic);
+                mAodIconContainer = nic;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 87d937b..4fbf077 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -84,6 +84,7 @@
         Dumpable {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
     @VisibleForTesting static final String TAG = "KeyguardStatusViewController";
+    private static final long STATUS_AREA_HEIGHT_ANIMATION_MILLIS = 133;
 
     /**
      * Duration to use for the animator when the keyguard status view alignment changes, and a
@@ -104,6 +105,10 @@
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final DozeParameters mDozeParameters;
+
+    private View mStatusArea = null;
+    private ValueAnimator mStatusAreaHeightAnimator = null;
 
     private Boolean mSplitShadeEnabled = false;
     private Boolean mStatusViewCentered = true;
@@ -123,6 +128,46 @@
                 }
             };
 
+    private final View.OnLayoutChangeListener mStatusAreaLayoutChangeListener =
+            new View.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v,
+                        int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom
+                ) {
+                    if (!mDozeParameters.getAlwaysOn()) {
+                        return;
+                    }
+
+                    int oldHeight = oldBottom - oldTop;
+                    int diff = v.getHeight() - oldHeight;
+                    if (diff == 0) {
+                        return;
+                    }
+
+                    int startValue = -1 * diff;
+                    long duration = STATUS_AREA_HEIGHT_ANIMATION_MILLIS;
+                    if (mStatusAreaHeightAnimator != null
+                            && mStatusAreaHeightAnimator.isRunning()) {
+                        duration += mStatusAreaHeightAnimator.getDuration()
+                                - mStatusAreaHeightAnimator.getCurrentPlayTime();
+                        startValue += (int) mStatusAreaHeightAnimator.getAnimatedValue();
+                        mStatusAreaHeightAnimator.cancel();
+                        mStatusAreaHeightAnimator = null;
+                    }
+
+                    mStatusAreaHeightAnimator = ValueAnimator.ofInt(startValue, 0);
+                    mStatusAreaHeightAnimator.setDuration(duration);
+                    final View nic = mKeyguardClockSwitchController.getAodNotifIconContainer();
+                    if (nic != null) {
+                        mStatusAreaHeightAnimator.addUpdateListener(anim -> {
+                            nic.setTranslationY((int) anim.getAnimatedValue());
+                        });
+                    }
+                    mStatusAreaHeightAnimator.start();
+                }
+            };
+
     @Inject
     public KeyguardStatusViewController(
             KeyguardStatusView keyguardStatusView,
@@ -144,6 +189,7 @@
         mKeyguardClockSwitchController = keyguardClockSwitchController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mConfigurationController = configurationController;
+        mDozeParameters = dozeParameters;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                 dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
                 logger.getBuffer());
@@ -218,12 +264,15 @@
 
     @Override
     protected void onViewAttached() {
+        mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+        mStatusArea.addOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
         mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
         mConfigurationController.addCallback(mConfigurationListener);
     }
 
     @Override
     protected void onViewDetached() {
+        mStatusArea.removeOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
         mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
         mConfigurationController.removeCallback(mConfigurationListener);
     }
@@ -293,9 +342,15 @@
     /**
      * Get the height of the keyguard status view without the notification icon area, as that's
      * only visible on AOD.
+     *
+     * We internally animate height changes to the status area to prevent discontinuities in the
+     * doze animation introduced by the height suddenly changing due to smartpace.
      */
     public int getLockscreenHeight() {
-        return mView.getHeight() - mKeyguardClockSwitchController.getNotificationIconAreaHeight();
+        int heightAnimValue = mStatusAreaHeightAnimator == null ? 0 :
+                (int) mStatusAreaHeightAnimator.getAnimatedValue();
+        return mView.getHeight() + heightAnimValue
+                - mKeyguardClockSwitchController.getNotificationIconAreaHeight();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 99529a1..9a50d83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -31,6 +31,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
@@ -49,12 +50,22 @@
         transitionInteractor.startedKeyguardState.map { keyguardState ->
             keyguardState == KeyguardState.AOD
         }
+
+    private fun getColor(usingBackgroundProtection: Boolean): Int {
+        return if (usingBackgroundProtection) {
+            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+        } else {
+            Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+        }
+    }
+
     private val color: Flow<Int> =
-        configurationRepository.onAnyConfigurationChange
-            .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) }
-            .onStart {
-                emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
-            }
+        deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection ->
+            configurationRepository.onAnyConfigurationChange
+                .map { getColor(useBgProtection) }
+                .onStart { emit(getColor(useBgProtection)) }
+        }
+
     private val useAodIconVariant: Flow<Boolean> =
         combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) {
                 isTransitionToAod,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 146715d..13fb42c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.power.data.repository.FakePowerRepository;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -70,6 +71,7 @@
 
     @Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
     @Mock protected FrameLayout mMediaHostContainer;
+    @Mock protected KeyguardStatusAreaView mKeyguardStatusAreaView;
 
     @Before
     public void setup() {
@@ -109,6 +111,8 @@
         when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
         when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
         when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
+        when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area))
+                .thenReturn(mKeyguardStatusAreaView);
     }
 
     protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 948942f..9c3288b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static junit.framework.Assert.assertEquals;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -27,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.AnimatorTestRule;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -40,6 +43,7 @@
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -51,6 +55,9 @@
 @RunWith(AndroidTestingRunner.class)
 public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest {
 
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
     @Test
     public void dozeTimeTick_updatesSlice() {
         mController.dozeTimeTick();
@@ -230,4 +237,34 @@
             throw new RuntimeException(e);
         }
     }
+
+    @Test
+    public void statusAreaHeightChange_animatesHeightOutputChange() {
+        // Init & Capture Layout Listener
+        mController.onInit();
+        mController.onViewAttached();
+
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mKeyguardStatusAreaView).addOnLayoutChangeListener(captor.capture());
+        View.OnLayoutChangeListener listener = captor.getValue();
+
+        // Setup and validate initial height
+        when(mKeyguardStatusView.getHeight()).thenReturn(200);
+        when(mKeyguardClockSwitchController.getNotificationIconAreaHeight()).thenReturn(10);
+        assertEquals(190, mController.getLockscreenHeight());
+
+        // Trigger Change and validate value unchanged immediately
+        when(mKeyguardStatusAreaView.getHeight()).thenReturn(100);
+        when(mKeyguardStatusView.getHeight()).thenReturn(300);      // Include child height
+        listener.onLayoutChange(mKeyguardStatusAreaView,
+            /* new layout */ 100, 300, 200, 400,
+            /* old layout */ 100, 300, 200, 300);
+        assertEquals(190, mController.getLockscreenHeight());
+
+        // Complete animation, validate height increased
+        mAnimatorTestRule.advanceTimeBy(200);
+        assertEquals(290, mController.getLockscreenHeight());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
index a7e7dd0..2b51ac5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.core.animation.doOnEnd
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.doOnEnd
@@ -30,6 +31,7 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 @RunWithLooper
+@FlakyTest(bugId = 302149604)
 class AnimatorTestRuleOrderTest : SysuiTestCase() {
 
     @get:Rule val animatorTestRule = AnimatorTestRule()
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bae06347..c2a1b6b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4021,11 +4021,8 @@
                 Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e);
                 return false;
             }
-            final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
-                    SystemUiSystemPropertiesFlags.NotificationFlags
-                            .SHOW_STICKY_HUN_FOR_DENIED_FSI);
             return checkUseFullScreenIntentPermission(attributionSource, applicationInfo,
-                    showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */);
+                    false /* forDataDelivery */);
         }
 
         @Override
@@ -7274,28 +7271,12 @@
         notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
 
         if (notification.fullScreenIntent != null) {
-            final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled(
-                    SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE);
-            if (forceDemoteFsiToStickyHun) {
+            final AttributionSource attributionSource =
+                    new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
+            final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
+                    attributionSource, ai, true /* forDataDelivery */);
+            if (!canUseFullScreenIntent) {
                 makeStickyHun(notification, pkg, userId);
-            } else {
-                final AttributionSource attributionSource =
-                        new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
-                final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
-                        SystemUiSystemPropertiesFlags.NotificationFlags
-                                .SHOW_STICKY_HUN_FOR_DENIED_FSI);
-                final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
-                        attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */,
-                        true /* forDataDelivery */);
-                if (!canUseFullScreenIntent) {
-                    if (showStickyHunIfDenied) {
-                        makeStickyHun(notification, pkg, userId);
-                    } else {
-                        notification.fullScreenIntent = null;
-                        Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
-                                + "USE_FULL_SCREEN_INTENT permission");
-                    }
-                }
             }
         }
 
@@ -7402,27 +7383,20 @@
     }
 
     private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
-            @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission,
+            @NonNull ApplicationInfo applicationInfo,
             boolean forDataDelivery) {
         if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
             return true;
         }
-        if (isAppOpPermission) {
-            final int permissionResult;
-            if (forDataDelivery) {
-                permissionResult = mPermissionManager.checkPermissionForDataDelivery(
-                        permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
-            } else {
-                permissionResult = mPermissionManager.checkPermissionForPreflight(
-                        permission.USE_FULL_SCREEN_INTENT, attributionSource);
-            }
-            return permissionResult == PermissionManager.PERMISSION_GRANTED;
+        final int permissionResult;
+        if (forDataDelivery) {
+            permissionResult = mPermissionManager.checkPermissionForDataDelivery(
+                    permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
         } else {
-            final int permissionResult = getContext().checkPermission(
-                    permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(),
-                    attributionSource.getUid());
-            return permissionResult == PERMISSION_GRANTED;
+            permissionResult = mPermissionManager.checkPermissionForPreflight(
+                    permission.USE_FULL_SCREEN_INTENT, attributionSource);
         }
+        return permissionResult == PermissionManager.PERMISSION_GRANTED;
     }
 
     private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index d2e980b..9a6ea2c 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -530,16 +530,13 @@
             this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter();
             this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r);
 
-            final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
-                    .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
             final boolean hasFullScreenIntent =
                     p.r.getSbn().getNotification().fullScreenIntent != null;
 
             final boolean hasFsiRequestedButDeniedFlag =  (p.r.getSbn().getNotification().flags
                     & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0;
 
-            this.fsi_state = NotificationRecordLogger.getFsiState(isStickyHunFlagEnabled,
+            this.fsi_state = NotificationRecordLogger.getFsiState(
                     hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType);
 
             this.is_locked = p.r.isLocked();
@@ -587,13 +584,10 @@
      * @return FrameworkStatsLog enum of the state of the full screen intent posted with this
      * notification.
      */
-    static int getFsiState(boolean isStickyHunFlagEnabled,
-                           boolean hasFullScreenIntent,
+    static int getFsiState(boolean hasFullScreenIntent,
                            boolean hasFsiRequestedButDeniedFlag,
                            NotificationReportedEvent eventType) {
-
-        if (!isStickyHunFlagEnabled
-                || eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
+        if (eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
             // Zeroes in protos take zero bandwidth, but non-zero numbers take bandwidth,
             // so we should log 0 when possible.
             return 0;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 783e9bb..252664a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2143,10 +2143,7 @@
      * @return State of the full screen intent permission for this package.
      */
     @VisibleForTesting
-    int getFsiState(String pkg, int uid, boolean requestedFSIPermission, boolean isFlagEnabled) {
-        if (!isFlagEnabled) {
-            return 0;
-        }
+    int getFsiState(String pkg, int uid, boolean requestedFSIPermission) {
         if (!requestedFSIPermission) {
             return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
         }
@@ -2167,10 +2164,8 @@
      * the user.
      */
     @VisibleForTesting
-    boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags,
-                                   boolean isStickyHunFlagEnabled) {
-        if (!isStickyHunFlagEnabled
-                || fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
+    boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags) {
+        if (fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
             return false;
         }
         return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
@@ -2213,22 +2208,18 @@
                     pkgsWithPermissionsToHandle.remove(key);
                 }
 
-                final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
-                        .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
                 final boolean requestedFSIPermission = mPermissionHelper.hasRequestedPermission(
                         android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, r.uid);
 
-                final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission,
-                        isStickyHunFlagEnabled);
+                final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission);
 
                 final int currentPermissionFlags = mPm.getPermissionFlags(
                         android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
                         UserHandle.getUserHandleForUid(r.uid));
 
                 final boolean fsiIsUserSet =
-                        isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags,
-                                isStickyHunFlagEnabled);
+                        isFsiPermissionUserSet(r.pkg, r.uid, fsiState,
+                                currentPermissionFlags);
 
                 events.add(FrameworkStatsLog.buildStatsEvent(
                         PACKAGE_NOTIFICATION_PREFERENCES,
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 79cd2a0..92d469c 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -259,6 +259,19 @@
      */
     boolean shouldFilterApplicationIncludingUninstalled(@Nullable PackageStateInternal ps,
             int callingUid, int userId);
+
+    /**
+     * Different from
+     * {@link #shouldFilterApplicationIncludingUninstalled(PackageStateInternal, int, int)}, the
+     * function returns {@code true} if:
+     * <ul>
+     * <li>The target package is not archived.
+     * <li>The package cannot be found in the device or has been uninstalled in the current user.
+     * </ul>
+     */
+    boolean shouldFilterApplicationIncludingUninstalledNotArchived(
+            @Nullable PackageStateInternal ps,
+            int callingUid, int userId);
     /**
      * Different from {@link #shouldFilterApplication(SharedUserSetting, int, int)}, the function
      * returns {@code true} if packages with the same shared user are all uninstalled in the current
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 11a6d1b..e5c4ccc 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2455,7 +2455,8 @@
      */
     public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
             int callingUid, @Nullable ComponentName component,
-            @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) {
+            @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall,
+            boolean filterArchived) {
         if (Process.isSdkSandboxUid(callingUid)) {
             int clientAppUid = Process.getAppUidForSdkSandboxUid(callingUid);
             // SDK sandbox should be able to see it's client app
@@ -2469,14 +2470,20 @@
         }
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
         final boolean callerIsInstantApp = instantAppPkgName != null;
+        final boolean packageArchivedForUser = ps != null && PackageArchiver.isArchived(
+                ps.getUserStateOrDefault(userId));
         // Don't treat hiddenUntilInstalled as an uninstalled state, phone app needs to access
         // these hidden application details to customize carrier apps. Also, allowing the system
         // caller accessing to application across users.
         if (ps == null
                 || (filterUninstall
-                        && !isSystemOrRootOrShell(callingUid)
-                        && !ps.isHiddenUntilInstalled()
-                        && !ps.getUserStateOrDefault(userId).isInstalled())) {
+                && !isSystemOrRootOrShell(callingUid)
+                && !ps.isHiddenUntilInstalled()
+                && !ps.getUserStateOrDefault(userId).isInstalled()
+                // Archived packages behave like uninstalled packages. So if filterUninstall is
+                // set to true, we dismiss filtering some uninstalled package only if it is
+                // archived and filterArchived is set as false.
+                && (!packageArchivedForUser || filterArchived))) {
             // If caller is instant app or sdk sandbox and ps is null, pretend the application
             // exists, but, needs to be filtered
             return (callerIsInstantApp || filterUninstall || Process.isSdkSandboxUid(callingUid));
@@ -2524,7 +2531,20 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
+     */
+    public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
+            int callingUid, @Nullable ComponentName component,
+            @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) {
+        return shouldFilterApplication(
+                ps, callingUid, component, componentType, userId, filterUninstall,
+                true /* filterArchived */);
+    }
+
+    /**
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
             int callingUid, @Nullable ComponentName component,
@@ -2534,7 +2554,8 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplication(
             @Nullable PackageStateInternal ps, int callingUid, int userId) {
@@ -2543,7 +2564,8 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplication(@NonNull SharedUserSetting sus,
             int callingUid, int userId) {
@@ -2558,7 +2580,8 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplicationIncludingUninstalled(
             @Nullable PackageStateInternal ps, int callingUid, int userId) {
@@ -2567,7 +2590,19 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
+     */
+    public final boolean shouldFilterApplicationIncludingUninstalledNotArchived(
+            @Nullable PackageStateInternal ps, int callingUid, int userId) {
+        return shouldFilterApplication(
+                ps, callingUid, null, TYPE_UNKNOWN, userId, true /* filterUninstall */,
+                false /* filterArchived */);
+    }
+
+    /**
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplicationIncludingUninstalled(
             @NonNull SharedUserSetting sus, int callingUid, int userId) {
@@ -5015,7 +5050,7 @@
         String installerPackageName = installSource.mInstallerPackageName;
         if (installerPackageName != null) {
             final PackageStateInternal ps = mSettings.getPackage(installerPackageName);
-            if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid,
+            if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid,
                     UserHandle.getUserId(callingUid))) {
                 installerPackageName = null;
             }
@@ -5033,7 +5068,8 @@
             return InstallSource.EMPTY;
         }
 
-        if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
+        if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid,
+                userId)) {
             return null;
         }
 
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 65c6329..3b3d79e 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -440,7 +440,7 @@
         if (outInfo != null) {
             // Remember which users are affected, before the installed states are modified
             outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL)
-                    ? ps.queryInstalledUsers(allUserHandles, /* installed= */true)
+                    ? ps.queryUsersInstalledOrHasData(allUserHandles)
                     : new int[]{userId};
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index eff6157..d2a4c27 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -37,6 +37,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.ArchivedActivityParcel;
 import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.LauncherActivityInfo;
@@ -140,6 +141,8 @@
         }
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "archiveApp");
+        verifyUninstallPermissions();
+
         CompletableFuture<ArchiveState> archiveStateFuture;
         try {
             archiveStateFuture = createArchiveState(packageName, userId);
@@ -182,6 +185,7 @@
             throws PackageManager.NameNotFoundException {
         PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
                 Binder.getCallingUid(), userId);
+        verifyNotSystemApp(ps.getFlags());
         String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
         verifyInstaller(responsibleInstallerPackage, userId);
         verifyOptOutStatus(packageName,
@@ -316,6 +320,13 @@
         return intentReceivers != null && !intentReceivers.getList().isEmpty();
     }
 
+    private void verifyNotSystemApp(int flags) throws PackageManager.NameNotFoundException {
+        if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || (
+                (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+            throw new PackageManager.NameNotFoundException("System apps cannot be archived.");
+        }
+    }
+
     /**
      * Returns true if the app is archivable.
      */
@@ -337,6 +348,11 @@
             throw new ParcelableException(e);
         }
 
+        if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0 || (
+                (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+            return false;
+        }
+
         if (isAppOptedOutOfArchiving(packageName, ps.getAppId())) {
             return false;
         }
@@ -372,9 +388,11 @@
     void requestUnarchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
+            @NonNull IntentSender statusReceiver,
             @NonNull UserHandle userHandle) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(callerPackageName);
+        Objects.requireNonNull(statusReceiver);
         Objects.requireNonNull(userHandle);
 
         Computer snapshot = mPm.snapshotComputer();
@@ -385,6 +403,8 @@
         }
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "unarchiveApp");
+        verifyInstallPermissions();
+
         PackageStateInternal ps;
         try {
             ps = getPackageState(packageName, snapshot, binderUid, userId);
@@ -400,9 +420,12 @@
                                     packageName)));
         }
 
+        // TODO(b/305902395) Introduce a confirmation dialog if the requestor only holds
+        // REQUEST_INSTALL permission.
         int draftSessionId;
         try {
-            draftSessionId = createDraftSession(packageName, installerPackage, userId);
+            draftSessionId = createDraftSession(packageName, installerPackage, statusReceiver,
+                    userId);
         } catch (RuntimeException e) {
             if (e.getCause() instanceof IOException) {
                 throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -415,11 +438,36 @@
                 () -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId));
     }
 
-    private int createDraftSession(String packageName, String installerPackage, int userId) {
+    private void verifyInstallPermissions() {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+                Manifest.permission.REQUEST_INSTALL_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("You need the com.android.permission.INSTALL_PACKAGES "
+                    + "or com.android.permission.REQUEST_INSTALL_PACKAGES permission to request "
+                    + "an unarchival.");
+        }
+    }
+
+    private void verifyUninstallPermissions() {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+                Manifest.permission.REQUEST_DELETE_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("You need the com.android.permission.DELETE_PACKAGES "
+                    + "or com.android.permission.REQUEST_DELETE_PACKAGES permission to request "
+                    + "an archival.");
+        }
+    }
+
+    private int createDraftSession(String packageName, String installerPackage,
+            IntentSender statusReceiver, int userId) {
         PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
         sessionParams.setAppPackageName(packageName);
         sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+        sessionParams.unarchiveIntentSender = statusReceiver;
+
         int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
         // Handles case of repeated unarchival calls for the same package.
         int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index af43a8b..c9663fc 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -16,8 +16,14 @@
 
 package com.android.server.pm;
 
+import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
 import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
@@ -37,6 +43,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PackageDeleteObserver;
+import android.app.PendingIntent;
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -56,6 +63,7 @@
 import android.content.pm.PackageInstaller.InstallConstraintsResult;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageInstaller.UnarchivalStatus;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
@@ -71,6 +79,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelableException;
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
@@ -1630,8 +1639,10 @@
     public void requestUnarchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
+            @NonNull IntentSender statusReceiver,
             @NonNull UserHandle userHandle) {
-        mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle);
+        mPackageArchiver.requestUnarchive(packageName, callerPackageName, statusReceiver,
+                userHandle);
     }
 
     @Override
@@ -1689,6 +1700,102 @@
         }
     }
 
+    // TODO(b/307299702) Implement error dialog and propagate userActionIntent.
+    @Override
+    public void reportUnarchivalStatus(
+            int unarchiveId,
+            @UnarchivalStatus int status,
+            long requiredStorageBytes,
+            @Nullable PendingIntent userActionIntent,
+            @NonNull UserHandle userHandle) {
+        verifyReportUnarchiveStatusInput(
+                status, requiredStorageBytes, userActionIntent, userHandle);
+
+        int userId = userHandle.getIdentifier();
+        int binderUid = Binder.getCallingUid();
+
+        synchronized (mSessions) {
+            PackageInstallerSession session = mSessions.get(unarchiveId);
+            if (session == null || session.userId != userId
+                    || session.params.appPackageName == null) {
+                throw new ParcelableException(new PackageManager.NameNotFoundException(
+                        TextUtils.formatSimple(
+                                "No valid session with unarchival ID %s found for user %s.",
+                                unarchiveId, userId)));
+            }
+
+            if (!isCallingUidOwner(session)) {
+                throw new SecurityException(TextUtils.formatSimple(
+                        "The caller UID %s does not have access to the session with unarchiveId "
+                                + "%d.",
+                        binderUid, unarchiveId));
+            }
+
+            IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender;
+            if (unarchiveIntentSender == null) {
+                throw new IllegalStateException(
+                        TextUtils.formatSimple(
+                                "Unarchival status for ID %s has already been set or a "
+                                        + "session has been created for it already by the "
+                                        + "caller.",
+                                unarchiveId));
+            }
+
+            // Execute expensive calls outside the sync block.
+            mPm.mHandler.post(
+                    () -> notifyUnarchivalListener(status, session.params.appPackageName,
+                            unarchiveIntentSender));
+            session.params.unarchiveIntentSender = null;
+            if (status != UNARCHIVAL_OK) {
+                Binder.withCleanCallingIdentity(session::abandon);
+            }
+        }
+    }
+
+    private static void verifyReportUnarchiveStatusInput(int status, long requiredStorageBytes,
+            @Nullable PendingIntent userActionIntent,
+            @NonNull UserHandle userHandle) {
+        Objects.requireNonNull(userHandle);
+        if (status == UNARCHIVAL_ERROR_USER_ACTION_NEEDED) {
+            Objects.requireNonNull(userActionIntent);
+        }
+        if (status == UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes <= 0) {
+            throw new IllegalStateException(
+                    "Insufficient storage error set, but requiredStorageBytes unspecified.");
+        }
+        if (status != UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes > 0) {
+            throw new IllegalStateException(
+                    TextUtils.formatSimple("requiredStorageBytes set, but error is %s.", status)
+            );
+        }
+        if (!List.of(
+                UNARCHIVAL_OK,
+                UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+                UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+                UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+                UNARCHIVAL_GENERIC_ERROR).contains(status)) {
+            throw new IllegalStateException("Invalid status code passed " + status);
+        }
+    }
+
+    private void notifyUnarchivalListener(int status, String packageName,
+            IntentSender unarchiveIntentSender) {
+        final Intent fillIn = new Intent();
+        fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
+        fillIn.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, status);
+        // TODO(b/307299702) Attach failure dialog with EXTRA_INTENT and requiredStorageBytes here.
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setPendingIntentBackgroundActivityStartMode(
+                MODE_BACKGROUND_ACTIVITY_START_DENIED);
+        try {
+            unarchiveIntentSender.sendIntent(mContext, 0, fillIn, /* onFinished= */ null,
+                    /* handler= */ null, /* requiredPermission= */ null,
+                    options.toBundle());
+        } catch (SendIntentException e) {
+            Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
+        }
+    }
+
     private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
             int installerUid) {
         int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 6f45d2b..f992bd8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4689,10 +4689,12 @@
 
         final int translatedUserId =
                 translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
+        final LocalIntentReceiver receiver = new LocalIntentReceiver();
 
         try {
             mInterface.getPackageInstaller().requestUnarchive(packageName,
-                    /* callerPackageName= */ "", new UserHandle(translatedUserId));
+                    /* callerPackageName= */ "", receiver.getIntentSender(),
+                    new UserHandle(translatedUserId));
         } catch (Exception e) {
             pw.println("Failure [" + e.getMessage() + "]");
             return 1;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 72090f2..b50d0a0 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -779,6 +779,10 @@
         return readUserState(userId).isInstalled();
     }
 
+    boolean isArchived(int userId) {
+        return PackageArchiver.isArchived(readUserState(userId));
+    }
+
     int getInstallReason(int userId) {
         return readUserState(userId).getInstallReason();
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4e14c90..8556317 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1592,6 +1592,19 @@
      */
     private void showConfirmCredentialToDisableQuietMode(
             @UserIdInt int userId, @Nullable IntentSender target) {
+        if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
+            // TODO (b/308121702) It may be brittle to rely on user states to check profile state
+            int state;
+            synchronized (mUserStates) {
+                state = mUserStates.get(userId, UserState.STATE_NONE);
+            }
+            if (state != UserState.STATE_NONE) {
+                Slog.i(LOG_TAG,
+                        "showConfirmCredentialToDisableQuietMode() called too early, user " + userId
+                                + " is still alive.");
+                return;
+            }
+        }
         // otherwise, we show a profile challenge to trigger decryption of the user
         final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
                 Context.KEYGUARD_SERVICE);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index ee46ce1..b3672ec 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.webkit;
 
+import static android.webkit.Flags.updateServiceV2;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -51,7 +53,7 @@
     private static final String TAG = "WebViewUpdateService";
 
     private BroadcastReceiver mWebViewUpdatedReceiver;
-    private WebViewUpdateServiceImpl mImpl;
+    private WebViewUpdateServiceInterface mImpl;
 
     static final int PACKAGE_CHANGED = 0;
     static final int PACKAGE_ADDED = 1;
@@ -60,7 +62,11 @@
 
     public WebViewUpdateService(Context context) {
         super(context);
-        mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+        if (updateServiceV2()) {
+            mImpl = new WebViewUpdateServiceImpl2(context, SystemImpl.getInstance());
+        } else {
+            mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 43d62aa..cfdef14 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -63,7 +63,7 @@
  *
  * @hide
  */
-class WebViewUpdateServiceImpl {
+class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
     private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName();
 
     private static class WebViewPackageMissingException extends Exception {
@@ -112,7 +112,8 @@
         mSystemInterface = systemInterface;
     }
 
-    void packageStateChanged(String packageName, int changedState, int userId) {
+    @Override
+    public void packageStateChanged(String packageName, int changedState, int userId) {
         // We don't early out here in different cases where we could potentially early-out (e.g. if
         // we receive PACKAGE_CHANGED for another user than the system user) since that would
         // complicate this logic further and open up for more edge cases.
@@ -163,7 +164,8 @@
         }
     }
 
-    void prepareWebViewInSystemServer() {
+    @Override
+    public void prepareWebViewInSystemServer() {
         mSystemInterface.notifyZygote(isMultiProcessEnabled());
         try {
             synchronized (mLock) {
@@ -210,7 +212,8 @@
         mSystemInterface.ensureZygoteStarted();
     }
 
-    void handleNewUser(int userId) {
+    @Override
+    public void handleNewUser(int userId) {
         // The system user is always started at boot, and by that point we have already run one
         // round of the package-changing logic (through prepareWebViewInSystemServer()), so early
         // out here.
@@ -218,7 +221,8 @@
         handleUserChange();
     }
 
-    void handleUserRemoved(int userId) {
+    @Override
+    public void handleUserRemoved(int userId) {
         handleUserChange();
     }
 
@@ -232,14 +236,16 @@
         updateCurrentWebViewPackage(null);
     }
 
-    void notifyRelroCreationCompleted() {
+    @Override
+    public void notifyRelroCreationCompleted() {
         synchronized (mLock) {
             mNumRelroCreationsFinished++;
             checkIfRelrosDoneLocked();
         }
     }
 
-    WebViewProviderResponse waitForAndGetProvider() {
+    @Override
+    public WebViewProviderResponse waitForAndGetProvider() {
         PackageInfo webViewPackage = null;
         final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
         boolean webViewReady = false;
@@ -284,7 +290,8 @@
      * replacing that provider it will not be in use directly, but will be used when the relros
      * or the replacement are done).
      */
-    String changeProviderAndSetting(String newProviderName) {
+    @Override
+    public String changeProviderAndSetting(String newProviderName) {
         PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
         if (newPackage == null) return "";
         return newPackage.packageName;
@@ -367,7 +374,8 @@
     /**
      * Fetch only the currently valid WebView packages.
      **/
-    WebViewProviderInfo[] getValidWebViewPackages() {
+    @Override
+    public WebViewProviderInfo[] getValidWebViewPackages() {
         ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
         WebViewProviderInfo[] providers =
             new WebViewProviderInfo[providersAndPackageInfos.length];
@@ -464,11 +472,13 @@
         return true;
     }
 
-    WebViewProviderInfo[] getWebViewPackages() {
+    @Override
+    public WebViewProviderInfo[] getWebViewPackages() {
         return mSystemInterface.getWebViewPackages();
     }
 
-    PackageInfo getCurrentWebViewPackage() {
+    @Override
+    public PackageInfo getCurrentWebViewPackage() {
         synchronized (mLock) {
             return mCurrentWebViewPackage;
         }
@@ -620,7 +630,8 @@
         return null;
     }
 
-    boolean isMultiProcessEnabled() {
+    @Override
+    public boolean isMultiProcessEnabled() {
         int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
         if (mSystemInterface.isMultiProcessDefaultEnabled()) {
             // Multiprocess should be enabled unless the user has turned it off manually.
@@ -631,7 +642,8 @@
         }
     }
 
-    void enableMultiProcess(boolean enable) {
+    @Override
+    public void enableMultiProcess(boolean enable) {
         PackageInfo current = getCurrentWebViewPackage();
         mSystemInterface.setMultiProcessSetting(mContext,
                 enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
@@ -644,7 +656,8 @@
     /**
      * Dump the state of this Service.
      */
-    void dumpState(PrintWriter pw) {
+    @Override
+    public void dumpState(PrintWriter pw) {
         pw.println("Current WebView Update Service state");
         pw.println(String.format("  Multiprocess enabled: %b", isMultiProcessEnabled()));
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
new file mode 100644
index 0000000..e618c7e
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package com.android.server.webkit;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.AsyncTask;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.webkit.UserPackage;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewProviderResponse;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the WebViewUpdateService.
+ * This class doesn't depend on the android system like the actual Service does and can be used
+ * directly by tests (as long as they implement a SystemInterface).
+ *
+ * This class keeps track of and prepares the current WebView implementation, and needs to keep
+ * track of a couple of different things such as what package is used as WebView implementation.
+ *
+ * The package-visible methods in this class are accessed from WebViewUpdateService either on the UI
+ * thread or on one of multiple Binder threads. The WebView preparation code shares state between
+ * threads meaning that code that chooses a new WebView implementation or checks which
+ * implementation is being used needs to hold a lock.
+ *
+ * The WebViewUpdateService can be accessed in a couple of different ways.
+ * 1. It is started from the SystemServer at boot - at that point we just initiate some state such
+ * as the WebView preparation class.
+ * 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot
+ * and the WebViewUpdateService should not have been accessed before this call. In this call we
+ * choose WebView implementation for the first time.
+ * 3. The update service listens for Intents related to package installs and removals. These intents
+ * are received and processed on the UI thread. Each intent can result in changing WebView
+ * implementation.
+ * 4. The update service can be reached through Binder calls which are handled on specific binder
+ * threads. These calls can be made from any process. Generally they are used for changing WebView
+ * implementation (from Settings), getting information about the current WebView implementation (for
+ * loading WebView into an app process), or notifying the service about Relro creation being
+ * completed.
+ *
+ * @hide
+ */
+class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
+    private static final String TAG = WebViewUpdateServiceImpl2.class.getSimpleName();
+
+    private static class WebViewPackageMissingException extends Exception {
+        WebViewPackageMissingException(String message) {
+            super(message);
+        }
+
+        WebViewPackageMissingException(Exception e) {
+            super(e);
+        }
+    }
+
+    private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
+    private static final long NS_PER_MS = 1000000;
+
+    private static final int VALIDITY_OK = 0;
+    private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
+    private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
+    private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
+    private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
+
+    private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE;
+    private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
+
+    private final SystemInterface mSystemInterface;
+    private final Context mContext;
+
+    private long mMinimumVersionCode = -1;
+
+    // Keeps track of the number of running relro creations
+    private int mNumRelroCreationsStarted = 0;
+    private int mNumRelroCreationsFinished = 0;
+    // Implies that we need to rerun relro creation because we are using an out-of-date package
+    private boolean mWebViewPackageDirty = false;
+    private boolean mAnyWebViewInstalled = false;
+
+    private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
+
+    // The WebView package currently in use (or the one we are preparing).
+    private PackageInfo mCurrentWebViewPackage = null;
+
+    private final Object mLock = new Object();
+
+    WebViewUpdateServiceImpl2(Context context, SystemInterface systemInterface) {
+        mContext = context;
+        mSystemInterface = systemInterface;
+    }
+
+    @Override
+    public void packageStateChanged(String packageName, int changedState, int userId) {
+        // We don't early out here in different cases where we could potentially early-out (e.g. if
+        // we receive PACKAGE_CHANGED for another user than the system user) since that would
+        // complicate this logic further and open up for more edge cases.
+        for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+            String webviewPackage = provider.packageName;
+
+            if (webviewPackage.equals(packageName)) {
+                boolean updateWebView = false;
+                boolean removedOrChangedOldPackage = false;
+                String oldProviderName = null;
+                PackageInfo newPackage = null;
+                synchronized (mLock) {
+                    try {
+                        newPackage = findPreferredWebViewPackage();
+                        if (mCurrentWebViewPackage != null) {
+                            oldProviderName = mCurrentWebViewPackage.packageName;
+                        }
+                        // Only trigger update actions if the updated package is the one
+                        // that will be used, or the one that was in use before the
+                        // update, or if we haven't seen a valid WebView package before.
+                        updateWebView =
+                            provider.packageName.equals(newPackage.packageName)
+                            || provider.packageName.equals(oldProviderName)
+                            || mCurrentWebViewPackage == null;
+                        // We removed the old package if we received an intent to remove
+                        // or replace the old package.
+                        removedOrChangedOldPackage =
+                            provider.packageName.equals(oldProviderName);
+                        if (updateWebView) {
+                            onWebViewProviderChanged(newPackage);
+                        }
+                    } catch (WebViewPackageMissingException e) {
+                        mCurrentWebViewPackage = null;
+                        Slog.e(TAG, "Could not find valid WebView package to create relro with "
+                                + e);
+                    }
+                }
+                if (updateWebView && !removedOrChangedOldPackage
+                        && oldProviderName != null) {
+                    // If the provider change is the result of adding or replacing a
+                    // package that was not the previous provider then we must kill
+                    // packages dependent on the old package ourselves. The framework
+                    // only kills dependents of packages that are being removed.
+                    mSystemInterface.killPackageDependents(oldProviderName);
+                }
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void prepareWebViewInSystemServer() {
+        mSystemInterface.notifyZygote(isMultiProcessEnabled());
+        try {
+            synchronized (mLock) {
+                mCurrentWebViewPackage = findPreferredWebViewPackage();
+                String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
+                if (userSetting != null
+                        && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
+                    // Don't persist the user-chosen setting across boots if the package being
+                    // chosen is not used (could be disabled or uninstalled) so that the user won't
+                    // be surprised by the device switching to using a certain webview package,
+                    // that was uninstalled/disabled a long time ago, if it is installed/enabled
+                    // again.
+                    mSystemInterface.updateUserSetting(mContext,
+                            mCurrentWebViewPackage.packageName);
+                }
+                onWebViewProviderChanged(mCurrentWebViewPackage);
+            }
+        } catch (Throwable t) {
+            // Log and discard errors at this stage as we must not crash the system server.
+            Slog.e(TAG, "error preparing webview provider from system server", t);
+        }
+
+        if (getCurrentWebViewPackage() == null) {
+            // We didn't find a valid WebView implementation. Try explicitly re-enabling the
+            // fallback package for all users in case it was disabled, even if we already did the
+            // one-time migration before. If this actually changes the state, we will see the
+            // PackageManager broadcast shortly and try again.
+            WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
+            WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
+            if (fallbackProvider != null) {
+                Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
+                mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
+                                                          true);
+            } else {
+                Slog.e(TAG, "No valid provider and no fallback available.");
+            }
+        }
+    }
+
+    private void startZygoteWhenReady() {
+        // Wait on a background thread for RELRO creation to be done. We ignore the return value
+        // because even if RELRO creation failed we still want to start the zygote.
+        waitForAndGetProvider();
+        mSystemInterface.ensureZygoteStarted();
+    }
+
+    @Override
+    public void handleNewUser(int userId) {
+        // The system user is always started at boot, and by that point we have already run one
+        // round of the package-changing logic (through prepareWebViewInSystemServer()), so early
+        // out here.
+        if (userId == UserHandle.USER_SYSTEM) return;
+        handleUserChange();
+    }
+
+    @Override
+    public void handleUserRemoved(int userId) {
+        handleUserChange();
+    }
+
+    /**
+     * Called when a user was added or removed to ensure WebView preparation is triggered.
+     * This has to be done since the WebView package we use depends on the enabled-state
+     * of packages for all users (so adding or removing a user might cause us to change package).
+     */
+    private void handleUserChange() {
+        // Potentially trigger package-changing logic.
+        updateCurrentWebViewPackage(null);
+    }
+
+    @Override
+    public void notifyRelroCreationCompleted() {
+        synchronized (mLock) {
+            mNumRelroCreationsFinished++;
+            checkIfRelrosDoneLocked();
+        }
+    }
+
+    @Override
+    public WebViewProviderResponse waitForAndGetProvider() {
+        PackageInfo webViewPackage = null;
+        final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
+        boolean webViewReady = false;
+        int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
+        synchronized (mLock) {
+            webViewReady = webViewIsReadyLocked();
+            while (!webViewReady) {
+                final long timeNowMs = System.nanoTime() / NS_PER_MS;
+                if (timeNowMs >= timeoutTimeMs) break;
+                try {
+                    mLock.wait(timeoutTimeMs - timeNowMs);
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+                webViewReady = webViewIsReadyLocked();
+            }
+            // Make sure we return the provider that was used to create the relro file
+            webViewPackage = mCurrentWebViewPackage;
+            if (webViewReady) {
+                // success
+            } else if (!mAnyWebViewInstalled) {
+                webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+            } else {
+                // Either the current relro creation  isn't done yet, or the new relro creatioin
+                // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
+                webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
+                String timeoutError = "Timed out waiting for relro creation, relros started "
+                        + mNumRelroCreationsStarted
+                        + " relros finished " + mNumRelroCreationsFinished
+                        + " package dirty? " + mWebViewPackageDirty;
+                Slog.e(TAG, timeoutError);
+                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, timeoutError);
+            }
+        }
+        if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
+        return new WebViewProviderResponse(webViewPackage, webViewStatus);
+    }
+
+    /**
+     * Change WebView provider and provider setting and kill packages using the old provider.
+     * Return the new provider (in case we are in the middle of creating relro files, or
+     * replacing that provider it will not be in use directly, but will be used when the relros
+     * or the replacement are done).
+     */
+    @Override
+    public String changeProviderAndSetting(String newProviderName) {
+        PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
+        if (newPackage == null) return "";
+        return newPackage.packageName;
+    }
+
+    /**
+     * Update the current WebView package.
+     * @param newProviderName the package to switch to, null if no package has been explicitly
+     * chosen.
+     */
+    private PackageInfo updateCurrentWebViewPackage(@Nullable String newProviderName) {
+        PackageInfo oldPackage = null;
+        PackageInfo newPackage = null;
+        boolean providerChanged = false;
+        synchronized (mLock) {
+            oldPackage = mCurrentWebViewPackage;
+
+            if (newProviderName != null) {
+                mSystemInterface.updateUserSetting(mContext, newProviderName);
+            }
+
+            try {
+                newPackage = findPreferredWebViewPackage();
+                providerChanged = (oldPackage == null)
+                        || !newPackage.packageName.equals(oldPackage.packageName);
+            } catch (WebViewPackageMissingException e) {
+                // If updated the Setting but don't have an installed WebView package, the
+                // Setting will be used when a package is available.
+                mCurrentWebViewPackage = null;
+                Slog.e(TAG, "Couldn't find WebView package to use " + e);
+                return null;
+            }
+            // Perform the provider change if we chose a new provider
+            if (providerChanged) {
+                onWebViewProviderChanged(newPackage);
+            }
+        }
+        // Kill apps using the old provider only if we changed provider
+        if (providerChanged && oldPackage != null) {
+            mSystemInterface.killPackageDependents(oldPackage.packageName);
+        }
+        // Return the new provider, this is not necessarily the one we were asked to switch to,
+        // but the persistent setting will now be pointing to the provider we were asked to
+        // switch to anyway.
+        return newPackage;
+    }
+
+    /**
+     * This is called when we change WebView provider, either when the current provider is
+     * updated or a new provider is chosen / takes precedence.
+     */
+    private void onWebViewProviderChanged(PackageInfo newPackage) {
+        synchronized (mLock) {
+            mAnyWebViewInstalled = true;
+            if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+                mCurrentWebViewPackage = newPackage;
+
+                // The relro creations might 'finish' (not start at all) before
+                // WebViewFactory.onWebViewProviderChanged which means we might not know the
+                // number of started creations before they finish.
+                mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
+                mNumRelroCreationsFinished = 0;
+                mNumRelroCreationsStarted =
+                    mSystemInterface.onWebViewProviderChanged(newPackage);
+                // If the relro creations finish before we know the number of started creations
+                // we will have to do any cleanup/notifying here.
+                checkIfRelrosDoneLocked();
+            } else {
+                mWebViewPackageDirty = true;
+            }
+        }
+
+        // Once we've notified the system that the provider has changed and started RELRO creation,
+        // try to restart the zygote so that it will be ready when apps use it.
+        if (isMultiProcessEnabled()) {
+            AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
+        }
+    }
+
+    /**
+     * Fetch only the currently valid WebView packages.
+     **/
+    @Override
+    public WebViewProviderInfo[] getValidWebViewPackages() {
+        ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
+        WebViewProviderInfo[] providers =
+                new WebViewProviderInfo[providersAndPackageInfos.length];
+        for (int n = 0; n < providersAndPackageInfos.length; n++) {
+            providers[n] = providersAndPackageInfos[n].provider;
+        }
+        return providers;
+    }
+
+    private static class ProviderAndPackageInfo {
+        public final WebViewProviderInfo provider;
+        public final PackageInfo packageInfo;
+
+        ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
+            this.provider = provider;
+            this.packageInfo = packageInfo;
+        }
+    }
+
+    private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
+        WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+        List<ProviderAndPackageInfo> providers = new ArrayList<>();
+        for (int n = 0; n < allProviders.length; n++) {
+            try {
+                PackageInfo packageInfo =
+                        mSystemInterface.getPackageInfoForProvider(allProviders[n]);
+                if (validityResult(allProviders[n], packageInfo) == VALIDITY_OK) {
+                    providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
+                }
+            } catch (NameNotFoundException e) {
+                // Don't add non-existent packages
+            }
+        }
+        return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
+    }
+
+    /**
+     * Returns either the package info of the WebView provider determined in the following way:
+     * If the user has chosen a provider then use that if it is valid,
+     * otherwise use the first package in the webview priority list that is valid.
+     *
+     */
+    private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
+        ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
+
+        String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
+
+        // If the user has chosen provider, use that (if it's installed and enabled for all
+        // users).
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
+                // userPackages can contain null objects.
+                List<UserPackage> userPackages =
+                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+                                providerAndPackage.provider);
+                if (isInstalledAndEnabledForAllUsers(userPackages)) {
+                    return providerAndPackage.packageInfo;
+                }
+            }
+        }
+
+        // User did not choose, or the choice failed; use the most stable provider that is
+        // installed and enabled for all users, and available by default (not through
+        // user choice).
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            if (providerAndPackage.provider.availableByDefault) {
+                // userPackages can contain null objects.
+                List<UserPackage> userPackages =
+                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+                                providerAndPackage.provider);
+                if (isInstalledAndEnabledForAllUsers(userPackages)) {
+                    return providerAndPackage.packageInfo;
+                }
+            }
+        }
+
+        // This should never happen during normal operation (only with modified system images).
+        mAnyWebViewInstalled = false;
+        throw new WebViewPackageMissingException("Could not find a loadable WebView package");
+    }
+
+    /**
+     * Return true iff {@param packageInfos} point to only installed and enabled packages.
+     * The given packages {@param packageInfos} should all be pointing to the same package, but each
+     * PackageInfo representing a different user's package.
+     */
+    private static boolean isInstalledAndEnabledForAllUsers(
+            List<UserPackage> userPackages) {
+        for (UserPackage userPackage : userPackages) {
+            if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public WebViewProviderInfo[] getWebViewPackages() {
+        return mSystemInterface.getWebViewPackages();
+    }
+
+    @Override
+    public PackageInfo getCurrentWebViewPackage() {
+        synchronized (mLock) {
+            return mCurrentWebViewPackage;
+        }
+    }
+
+    /**
+     * Returns whether WebView is ready and is not going to go through its preparation phase
+     * again directly.
+     */
+    private boolean webViewIsReadyLocked() {
+        return !mWebViewPackageDirty
+            && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
+            // The current package might be replaced though we haven't received an intent
+            // declaring this yet, the following flag makes anyone loading WebView to wait in
+            // this case.
+            && mAnyWebViewInstalled;
+    }
+
+    private void checkIfRelrosDoneLocked() {
+        if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+            if (mWebViewPackageDirty) {
+                mWebViewPackageDirty = false;
+                // If we have changed provider since we started the relro creation we need to
+                // redo the whole process using the new package instead.
+                try {
+                    PackageInfo newPackage = findPreferredWebViewPackage();
+                    onWebViewProviderChanged(newPackage);
+                } catch (WebViewPackageMissingException e) {
+                    mCurrentWebViewPackage = null;
+                    // If we can't find any valid WebView package we are now in a state where
+                    // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
+                    // should simply wait until we receive an intent declaring a new package was
+                    // installed.
+                }
+            } else {
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
+        // Ensure the provider targets this framework release (or a later one).
+        if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
+            return VALIDITY_INCORRECT_SDK_VERSION;
+        }
+        if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
+                && !mSystemInterface.systemIsDebuggable()) {
+            // Webview providers may be downgraded arbitrarily low, prevent that by enforcing
+            // minimum version code. This check is only enforced for user builds.
+            return VALIDITY_INCORRECT_VERSION_CODE;
+        }
+        if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) {
+            return VALIDITY_INCORRECT_SIGNATURE;
+        }
+        if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) {
+            return VALIDITY_NO_LIBRARY_FLAG;
+        }
+        return VALIDITY_OK;
+    }
+
+    /**
+     * Both versionCodes should be from a WebView provider package implemented by Chromium.
+     * VersionCodes from other kinds of packages won't make any sense in this method.
+     *
+     * An introduction to Chromium versionCode scheme:
+     * "BBBBPPPXX"
+     * BBBB: 4 digit branch number. It monotonically increases over time.
+     * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits
+     * may change their meaning in the future.
+     * XX: Digits to differentiate different APK builds of the same source version.
+     *
+     * This method takes the "BBBB" of versionCodes and compare them.
+     *
+     * https://www.chromium.org/developers/version-numbers describes general Chromium versioning;
+     * https://source.chromium.org/chromium/chromium/src/+/master:build/util/android_chrome_version.py
+     * is the canonical source for how Chromium versionCodes are calculated.
+     *
+     * @return true if versionCode1 is higher than or equal to versionCode2.
+     */
+    private static boolean versionCodeGE(long versionCode1, long versionCode2) {
+        long v1 = versionCode1 / 100000;
+        long v2 = versionCode2 / 100000;
+
+        return v1 >= v2;
+    }
+
+    /**
+     * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
+     * of all available-by-default WebView provider packages. If there is no such WebView provider
+     * package on the system, then return -1, which means all positive versionCode WebView packages
+     * are accepted.
+     *
+     * Note that this is a private method that handles a variable (mMinimumVersionCode) which is
+     * shared between threads. Furthermore, this method does not hold mLock meaning that we must
+     * take extra care to ensure this method is thread-safe.
+     */
+    private long getMinimumVersionCode() {
+        if (mMinimumVersionCode > 0) {
+            return mMinimumVersionCode;
+        }
+
+        long minimumVersionCode = -1;
+        for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+            if (provider.availableByDefault) {
+                try {
+                    long versionCode =
+                            mSystemInterface.getFactoryPackageVersion(provider.packageName);
+                    if (minimumVersionCode < 0 || versionCode < minimumVersionCode) {
+                        minimumVersionCode = versionCode;
+                    }
+                } catch (NameNotFoundException e) {
+                    // Safe to ignore.
+                }
+            }
+        }
+
+        mMinimumVersionCode = minimumVersionCode;
+        return mMinimumVersionCode;
+    }
+
+    private static boolean providerHasValidSignature(WebViewProviderInfo provider,
+            PackageInfo packageInfo, SystemInterface systemInterface) {
+        // Skip checking signatures on debuggable builds, for development purposes.
+        if (systemInterface.systemIsDebuggable()) return true;
+
+        // Allow system apps to be valid providers regardless of signature.
+        if (packageInfo.applicationInfo.isSystemApp()) return true;
+
+        // We don't support packages with multiple signatures.
+        if (packageInfo.signatures.length != 1) return false;
+
+        // If any of the declared signatures match the package signature, it's valid.
+        for (Signature signature : provider.signatures) {
+            if (signature.equals(packageInfo.signatures[0])) return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the only fallback provider in the set of given packages, or null if there is none.
+     */
+    private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
+        for (WebViewProviderInfo provider : webviewPackages) {
+            if (provider.isFallback) {
+                return provider;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isMultiProcessEnabled() {
+        int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
+        if (mSystemInterface.isMultiProcessDefaultEnabled()) {
+            // Multiprocess should be enabled unless the user has turned it off manually.
+            return settingValue > MULTIPROCESS_SETTING_OFF_VALUE;
+        } else {
+            // Multiprocess should not be enabled, unless the user has turned it on manually.
+            return settingValue >= MULTIPROCESS_SETTING_ON_VALUE;
+        }
+    }
+
+    @Override
+    public void enableMultiProcess(boolean enable) {
+        PackageInfo current = getCurrentWebViewPackage();
+        mSystemInterface.setMultiProcessSetting(mContext,
+                enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
+        mSystemInterface.notifyZygote(enable);
+        if (current != null) {
+            mSystemInterface.killPackageDependents(current.packageName);
+        }
+    }
+
+    /**
+     * Dump the state of this Service.
+     */
+    @Override
+    public void dumpState(PrintWriter pw) {
+        pw.println("Current WebView Update Service state");
+        pw.println(String.format("  Multiprocess enabled: %b", isMultiProcessEnabled()));
+        synchronized (mLock) {
+            if (mCurrentWebViewPackage == null) {
+                pw.println("  Current WebView package is null");
+            } else {
+                pw.println(String.format("  Current WebView package (name, version): (%s, %s)",
+                        mCurrentWebViewPackage.packageName,
+                        mCurrentWebViewPackage.versionName));
+            }
+            pw.println(String.format("  Minimum targetSdkVersion: %d",
+                    UserPackage.MINIMUM_SUPPORTED_SDK));
+            pw.println(String.format("  Minimum WebView version code: %d",
+                    mMinimumVersionCode));
+            pw.println(String.format("  Number of relros started: %d",
+                    mNumRelroCreationsStarted));
+            pw.println(String.format("  Number of relros finished: %d",
+                        mNumRelroCreationsFinished));
+            pw.println(String.format("  WebView package dirty: %b", mWebViewPackageDirty));
+            pw.println(String.format("  Any WebView package installed: %b",
+                    mAnyWebViewInstalled));
+
+            try {
+                PackageInfo preferredWebViewPackage = findPreferredWebViewPackage();
+                pw.println(String.format(
+                        "  Preferred WebView package (name, version): (%s, %s)",
+                        preferredWebViewPackage.packageName,
+                        preferredWebViewPackage.versionName));
+            } catch (WebViewPackageMissingException e) {
+                pw.println(String.format("  Preferred WebView package: none"));
+            }
+
+            dumpAllPackageInformationLocked(pw);
+        }
+    }
+
+    private void dumpAllPackageInformationLocked(PrintWriter pw) {
+        WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+        pw.println("  WebView packages:");
+        for (WebViewProviderInfo provider : allProviders) {
+            List<UserPackage> userPackages =
+                    mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+            PackageInfo systemUserPackageInfo =
+                    userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
+            if (systemUserPackageInfo == null) {
+                pw.println(String.format("    %s is NOT installed.", provider.packageName));
+                continue;
+            }
+
+            int validity = validityResult(provider, systemUserPackageInfo);
+            String packageDetails = String.format(
+                    "versionName: %s, versionCode: %d, targetSdkVersion: %d",
+                    systemUserPackageInfo.versionName,
+                    systemUserPackageInfo.getLongVersionCode(),
+                    systemUserPackageInfo.applicationInfo.targetSdkVersion);
+            if (validity == VALIDITY_OK) {
+                boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
+                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider));
+                pw.println(String.format(
+                        "    Valid package %s (%s) is %s installed/enabled for all users",
+                        systemUserPackageInfo.packageName,
+                        packageDetails,
+                        installedForAllUsers ? "" : "NOT"));
+            } else {
+                pw.println(String.format("    Invalid package %s (%s), reason: %s",
+                        systemUserPackageInfo.packageName,
+                        packageDetails,
+                        getInvalidityReason(validity)));
+            }
+        }
+    }
+
+    private static String getInvalidityReason(int invalidityReason) {
+        switch (invalidityReason) {
+            case VALIDITY_INCORRECT_SDK_VERSION:
+                return "SDK version too low";
+            case VALIDITY_INCORRECT_VERSION_CODE:
+                return "Version code too low";
+            case VALIDITY_INCORRECT_SIGNATURE:
+                return "Incorrect signature";
+            case VALIDITY_NO_LIBRARY_FLAG:
+                return "No WebView-library manifest flag";
+            default:
+                return "Unexcepted validity-reason";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
new file mode 100644
index 0000000..a9c3dc4
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.server.webkit;
+
+import android.content.pm.PackageInfo;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewProviderResponse;
+
+import java.io.PrintWriter;
+
+interface WebViewUpdateServiceInterface {
+    void packageStateChanged(String packageName, int changedState, int userId);
+
+    void handleNewUser(int userId);
+
+    void handleUserRemoved(int userId);
+
+    WebViewProviderInfo[] getWebViewPackages();
+
+    void prepareWebViewInSystemServer();
+
+    void notifyRelroCreationCompleted();
+
+    WebViewProviderResponse waitForAndGetProvider();
+
+    String changeProviderAndSetting(String newProviderName);
+
+    WebViewProviderInfo[] getValidWebViewPackages();
+
+    PackageInfo getCurrentWebViewPackage();
+
+    boolean isMultiProcessEnabled();
+
+    void enableMultiProcess(boolean enable);
+
+    void dumpState(PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/webkit/flags.aconfig b/services/core/java/com/android/server/webkit/flags.aconfig
new file mode 100644
index 0000000..1411acc
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.webkit"
+
+flag {
+    name: "update_service_v2"
+    namespace: "webview"
+    description: "Using a new version of the WebView update service"
+    bug: "308907090"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 287aaf9..8cc197c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -46,7 +46,6 @@
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
-import android.compat.annotation.Overridable;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -88,7 +87,6 @@
     /** If enabled the creator will not allow BAL on its behalf by default. */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    @Overridable
     private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR =
             296478951;
     public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index d0d7f49..e6bbd52 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -101,10 +101,8 @@
         mPolicy = displayContent.getDisplayPolicy();
         final Resources r = mPolicy.getContext().getResources();
         mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
-        mTransientControlTarget = new ControlTarget(
-                stateController, displayContent.mWmService.mH, "TransientControlTarget");
-        mPermanentControlTarget = new ControlTarget(
-                stateController, displayContent.mWmService.mH, "PermanentControlTarget");
+        mTransientControlTarget = new ControlTarget(displayContent, "TransientControlTarget");
+        mPermanentControlTarget = new ControlTarget(displayContent, "PermanentControlTarget");
     }
 
     /** Updates the target which can control system bars. */
@@ -699,24 +697,35 @@
         }
     }
 
-    private static class ControlTarget implements InsetsControlTarget {
+    private static class ControlTarget implements InsetsControlTarget, Runnable {
 
+        private final Handler mHandler;
+        private final Object mGlobalLock;
         private final InsetsState mState = new InsetsState();
-        private final InsetsController mInsetsController;
         private final InsetsStateController mStateController;
+        private final InsetsController mInsetsController;
         private final String mName;
 
-        ControlTarget(InsetsStateController stateController, Handler handler, String name) {
-            mStateController = stateController;
-            mInsetsController = new InsetsController(new Host(handler, name));
+        ControlTarget(DisplayContent displayContent, String name) {
+            mHandler = displayContent.mWmService.mH;
+            mGlobalLock = displayContent.mWmService.mGlobalLock;
+            mStateController = displayContent.getInsetsStateController();
+            mInsetsController = new InsetsController(new Host(mHandler, name));
             mName = name;
         }
 
         @Override
         public void notifyInsetsControlChanged() {
-            mState.set(mStateController.getRawInsetsState(), true /* copySources */);
-            mInsetsController.onStateChanged(mState);
-            mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
+            mHandler.post(this);
+        }
+
+        @Override
+        public void run() {
+            synchronized (mGlobalLock) {
+                mState.set(mStateController.getRawInsetsState(), true /* copySources */);
+                mInsetsController.onStateChanged(mState);
+                mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index b3a3650..89a70e5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -43,8 +43,6 @@
 
     /* Start Available Flags */
 
-    final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag();
-
     final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
 
     final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index a9f5b14..18a2acc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
@@ -171,6 +172,12 @@
 
         when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
         when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100);
+        when(mContext.checkCallingOrSelfPermission(
+                eq(Manifest.permission.REQUEST_INSTALL_PACKAGES))).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+        when(mContext.checkCallingOrSelfPermission(
+                eq(Manifest.permission.REQUEST_DELETE_PACKAGES))).thenReturn(
+                PackageManager.PERMISSION_DENIED);
 
         when(mAppOpsManager.checkOp(
                 eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
@@ -386,7 +393,7 @@
         Exception e = assertThrows(
                 SecurityException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, "different",
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e).hasMessageThat().isEqualTo(
                 String.format(
                         "The UID %s of callerPackageName set by the caller doesn't match the "
@@ -404,7 +411,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s not found.", PACKAGE));
@@ -416,7 +423,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s is not currently archived.", PACKAGE));
@@ -428,7 +435,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s is not currently archived.", PACKAGE));
@@ -452,7 +459,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("No installer found to unarchive app %s.", PACKAGE));
@@ -462,7 +469,8 @@
     public void unarchiveApp_success() {
         mUserState.setArchiveState(createArchiveState()).setInstalled(false);
 
-        mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+        mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+                UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 7fb8b30..a47fbce 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -87,8 +87,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
@@ -739,9 +737,6 @@
         clearInvocations(mRankingHandler);
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
 
-        mTestFlagResolver.setFlagOverride(FSI_FORCE_DEMOTE, false);
-        mTestFlagResolver.setFlagOverride(SHOW_STICKY_HUN_FOR_DENIED_FSI, false);
-
         var checker = mock(TestableNotificationManagerService.ComponentPermissionChecker.class);
         mService.permissionChecker = checker;
         when(checker.check(anyString(), anyInt(), anyInt(), anyBoolean()))
@@ -11472,14 +11467,12 @@
         verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
     }
 
-    private void verifyStickyHun(Flag flag, int permissionState, boolean appRequested,
+    private void verifyStickyHun(int permissionState, boolean appRequested,
             boolean isSticky) throws Exception {
 
         when(mPermissionHelper.hasRequestedPermission(Manifest.permission.USE_FULL_SCREEN_INTENT,
                 PKG, mUserId)).thenReturn(appRequested);
 
-        mTestFlagResolver.setFlagOverride(flag, true);
-
         when(mPermissionManager.checkPermissionForDataDelivery(
                 eq(Manifest.permission.USE_FULL_SCREEN_INTENT), any(), any()))
                 .thenReturn(permissionState);
@@ -11503,8 +11496,7 @@
     public void testFixNotification_flagEnableStickyHun_fsiPermissionHardDenied_showStickyHun()
             throws Exception {
 
-        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
+        verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
                 /* isSticky= */ true);
     }
 
@@ -11512,16 +11504,14 @@
     public void testFixNotification_flagEnableStickyHun_fsiPermissionSoftDenied_showStickyHun()
             throws Exception {
 
-        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
+        verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
                 /* isSticky= */ true);
     }
 
     @Test
     public void testFixNotification_fsiPermissionSoftDenied_appNotRequest_noShowStickyHun()
             throws Exception {
-        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
+        verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
                 /* isSticky= */ false);
     }
 
@@ -11530,39 +11520,11 @@
     public void testFixNotification_flagEnableStickyHun_fsiPermissionGranted_showFsi()
             throws Exception {
 
-        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
+        verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
                 /* isSticky= */ false);
     }
 
     @Test
-    public void testFixNotification_flagForceStickyHun_fsiPermissionHardDenied_showStickyHun()
-            throws Exception {
-
-        verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
-                /* isSticky= */ true);
-    }
-
-    @Test
-    public void testFixNotification_flagForceStickyHun_fsiPermissionSoftDenied_showStickyHun()
-            throws Exception {
-
-        verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
-                /* isSticky= */ true);
-    }
-
-    @Test
-    public void testFixNotification_flagForceStickyHun_fsiPermissionGranted_showStickyHun()
-            throws Exception {
-
-        verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
-                /* isSticky= */ true);
-    }
-
-    @Test
     public void fixNotification_withFgsFlag_butIsNotFgs() throws Exception {
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
index 5147a08..29848d0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -171,19 +171,8 @@
     }
 
     @Test
-    public void testGetFsiState_stickyHunFlagDisabled_zero() {
-        final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ false,
-                /* hasFullScreenIntent= */ true,
-                /* hasFsiRequestedButDeniedFlag= */ true,
-                /* eventType= */ NOTIFICATION_POSTED);
-        assertEquals(0, fsiState);
-    }
-
-    @Test
     public void testGetFsiState_isUpdate_zero() {
         final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ true,
                 /* hasFullScreenIntent= */ true,
                 /* hasFsiRequestedButDeniedFlag= */ true,
                 /* eventType= */ NOTIFICATION_UPDATED);
@@ -193,7 +182,6 @@
     @Test
     public void testGetFsiState_hasFsi_allowedEnum() {
         final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ true,
                 /* hasFullScreenIntent= */ true,
                 /* hasFsiRequestedButDeniedFlag= */ false,
                 /* eventType= */ NOTIFICATION_POSTED);
@@ -203,7 +191,6 @@
     @Test
     public void testGetFsiState_fsiPermissionDenied_deniedEnum() {
         final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ true,
                 /* hasFullScreenIntent= */ false,
                 /* hasFsiRequestedButDeniedFlag= */ true,
                 /* eventType= */ NOTIFICATION_POSTED);
@@ -213,7 +200,6 @@
     @Test
     public void testGetFsiState_noFsi_noFsiEnum() {
         final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ true,
                 /* hasFullScreenIntent= */ false,
                 /* hasFsiRequestedButDeniedFlag= */ false,
                 /* eventType= */ NOTIFICATION_POSTED);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c156e37..fe21103 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -5733,17 +5733,9 @@
     }
 
     @Test
-    public void testGetFsiState_flagDisabled_zero() {
-        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission */ true, /* isFlagEnabled= */ false);
-
-        assertEquals(0, fsiState);
-    }
-
-    @Test
     public void testGetFsiState_appDidNotRequest_enumNotRequested() {
         final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission */ false, /* isFlagEnabled= */ true);
+                /* requestedFsiPermission */ false);
 
         assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, fsiState);
     }
@@ -5754,7 +5746,7 @@
                 .thenReturn(PermissionManager.PERMISSION_GRANTED);
 
         final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission= */ true, /* isFlagEnabled= */ true);
+                /* requestedFsiPermission= */ true);
 
         assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, fsiState);
     }
@@ -5765,7 +5757,7 @@
                 .thenReturn(PermissionManager.PERMISSION_SOFT_DENIED);
 
         final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+                /* requestedFsiPermission = */ true);
 
         assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
     }
@@ -5776,7 +5768,7 @@
                 .thenReturn(PermissionManager.PERMISSION_HARD_DENIED);
 
         final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+                /* requestedFsiPermission = */ true);
 
         assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
     }
@@ -5785,18 +5777,7 @@
     public void testIsFsiPermissionUserSet_appDidNotRequest_false() {
         final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
                 /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED,
-                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
-                /* isStickyHunFlagEnabled= */ true);
-
-        assertFalse(isUserSet);
-    }
-
-    @Test
-    public void testIsFsiPermissionUserSet_flagDisabled_false() {
-        final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
-                /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
-                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
-                /* isStickyHunFlagEnabled= */ false);
+                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET);
 
         assertFalse(isUserSet);
     }
@@ -5805,8 +5786,7 @@
     public void testIsFsiPermissionUserSet_userSet_true() {
         final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
                 /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
-                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
-                /* isStickyHunFlagEnabled= */ true);
+                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET);
 
         assertTrue(isUserSet);
     }
@@ -5815,8 +5795,7 @@
     public void testIsFsiPermissionUserSet_notUserSet_false() {
         final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
                 /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
-                /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET,
-                /* isStickyHunFlagEnabled= */ true);
+                /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET);
 
         assertFalse(isUserSet);
     }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 63de41f..4c56f33 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1953,6 +1953,13 @@
             }
         }
 
+        // Flags status.
+        pw.println("Flags:");
+        pw.println("    " + Flags.FLAG_USER_INTERACTION_TYPE_API
+                + ": " + Flags.userInteractionTypeApi());
+        pw.println("    " + Flags.FLAG_USE_PARCELED_LIST
+                + ": " + Flags.useParceledList());
+
         final int[] userIds;
         synchronized (mLock) {
             final int userCount = mUserState.size();
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f8608b8..e12a815 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -23,6 +23,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
 import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -70,6 +71,7 @@
 import com.android.internal.telephony.ISetOpportunisticDataCallback;
 import com.android.internal.telephony.ISub;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.HandlerExecutor;
 import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.Preconditions;
@@ -95,7 +97,22 @@
 import java.util.stream.Collectors;
 
 /**
- * Subscription manager provides the mobile subscription information.
+ * Subscription manager provides the mobile subscription information that are associated with the
+ * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
+ * and below can see all subscriptions as it does today.
+ *
+ * <p>For example, if we have
+ * <ul>
+ *     <li> Subscription 1 associated with personal profile.
+ *     <li> Subscription 2 associated with work profile.
+ * </ul>
+ * Then for SDK 35+, if the caller identity is personal profile, then
+ * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
+ *
+ * <p>If the caller needs to see all subscriptions across user profiles,
+ * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission
+ * may be required as documented on the each API.
+ *
  */
 @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1446,6 +1463,16 @@
         }
     }
 
+    /**
+     * {@code true} if the SubscriptionManager instance should see all subscriptions regardless its
+     * association with particular user profile.
+     *
+     * <p> It only applies to Android SDK 35(V) and above. For Android SDK 34(U) and below, the
+     * caller can see all subscription across user profiles as it does today today even if it's
+     * {@code false}.
+     */
+    private boolean mIsForAllUserProfiles = false;
+
     /** @hide */
     @UnsupportedAppUsage
     public SubscriptionManager(Context context) {
@@ -1776,8 +1803,23 @@
     }
 
     /**
-     * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted
-     * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
+     * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller
+     * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
+     * and below can see all subscriptions as it does today.
+     *
+     * <p>For example, if we have
+     * <ul>
+     *     <li> Subscription 1 associated with personal profile.
+     *     <li> Subscription 2 associated with work profile.
+     * </ul>
+     * Then for SDK 35+, if the caller identity is personal profile, then this will return
+     * subscription 1 only and vice versa.
+     *
+     * <p>If the caller needs to see all subscriptions across user profiles,
+     * use {@link #createForAllUserProfiles} to convert this instance to see all.
+     *
+     * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
+     * {@link SubscriptionInfo#getSubscriptionId}.
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see
@@ -1800,8 +1842,25 @@
      * </ul>
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    // @RequiresPermission(TODO(b/308809058))
     public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
-        return getActiveSubscriptionInfoList(/* userVisibleonly */true);
+        List<SubscriptionInfo> activeList = null;
+
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
+                        mContext.getAttributionTag(), mIsForAllUserProfiles);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        if (activeList != null) {
+            activeList = activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo))
+                    .collect(Collectors.toList());
+        }
+        return activeList;
     }
 
     /**
@@ -1835,6 +1894,26 @@
     }
 
     /**
+     * Convert this subscription manager instance into one that can see all subscriptions across
+     * user profiles.
+     *
+     * @return a SubscriptionManager that can see all subscriptions regardless its user profile
+     * association.
+     *
+     * @see #getActiveSubscriptionInfoList
+     * @see #getActiveSubscriptionInfoCount
+     * @see UserHandle
+     */
+    @FlaggedApi(Flags.FLAG_WORK_PROFILE_API_SPLIT)
+    // @RequiresPermission(TODO(b/308809058))
+    // The permission check for accessing all subscriptions will be enforced upon calling the
+    // individual APIs linked above.
+    @NonNull public SubscriptionManager createForAllUserProfiles() {
+        mIsForAllUserProfiles = true;
+        return this;
+    }
+
+    /**
     * This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly
     * is true, it will filter out the hidden subscriptions.
     *
@@ -1847,7 +1926,7 @@
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
                 activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
+                        mContext.getAttributionTag(), true /*isForAllUserProfiles*/);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -2002,13 +2081,19 @@
     }
 
     /**
-     * Get the active subscription count.
+     * Get the active subscription count associated with the current caller user profile for
+     * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
+     * it does today.
+     *
+     * <p>If the caller needs to see all subscriptions across user profiles,
+     * use {@link #createForAllUserProfiles} to convert this instance to see all.
      *
      * @return The current number of active subscriptions.
      *
      * @see #getActiveSubscriptionInfoList()
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    // @RequiresPermission(TODO(b/308809058))
     public int getActiveSubscriptionInfoCount() {
         int result = 0;
 
@@ -2016,7 +2101,7 @@
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
                 result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
+                        mContext.getAttributionTag(), mIsForAllUserProfiles);
             }
         } catch (RemoteException ex) {
             // ignore it
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index d2dbeb7..3f41d56 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -66,6 +66,8 @@
      *
      * @param callingPackage The package maing the call.
      * @param callingFeatureId The feature in the package
+     * @param isForAllProfiles whether the caller intends to see all subscriptions regardless
+     *                      association.
      * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
      * <ul>
      * <li>
@@ -83,14 +85,17 @@
      * </ul>
      */
     List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage,
-            String callingFeatureId);
+            String callingFeatureId, boolean isForAllProfiles);
 
     /**
      * @param callingPackage The package making the call.
      * @param callingFeatureId The feature in the package.
+     * @param isForAllProfile whether the caller intends to see all subscriptions regardless
+     *                      association.
      * @return the number of active subscriptions
      */
-    int getActiveSubInfoCount(String callingPackage, String callingFeatureId);
+    int getActiveSubInfoCount(String callingPackage, String callingFeatureId,
+            boolean isForAllProfile);
 
     /**
      * @return the maximum number of subscriptions this device will support at any one time.
diff --git a/tests/InputScreenshotTest/Android.bp b/tests/InputScreenshotTest/Android.bp
index 15aaa46..83ced2c 100644
--- a/tests/InputScreenshotTest/Android.bp
+++ b/tests/InputScreenshotTest/Android.bp
@@ -7,12 +7,27 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+filegroup {
+    name: "InputScreenshotTestRNGFiles",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    exclude_srcs: [
+        "src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt",
+        "src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt",
+    ],
+}
+
 android_test {
     name: "InputScreenshotTests",
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
     ],
+    exclude_srcs: [
+        "src/android/input/screenshot/package-info.java",
+    ],
     platform_apis: true,
     certificate: "platform",
     static_libs: [
@@ -43,6 +58,7 @@
         "hamcrest-library",
         "kotlin-test",
         "flag-junit",
+        "platform-parametric-runner-lib",
         "platform-test-annotations",
         "services.core.unboosted",
         "testables",
diff --git a/tests/InputScreenshotTest/robotests/Android.bp b/tests/InputScreenshotTest/robotests/Android.bp
new file mode 100644
index 0000000..912f4b80
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/Android.bp
@@ -0,0 +1,71 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "InputRoboRNGTestsAssetsLib",
+    asset_dirs: ["assets"],
+    sdk_version: "current",
+    platform_apis: true,
+    manifest: "AndroidManifest.xml",
+    optimize: {
+        enabled: false,
+    },
+    use_resource_processor: true,
+}
+
+android_app {
+    name: "InputRoboApp",
+    srcs: [],
+    static_libs: [
+        "androidx.test.espresso.core",
+        "androidx.appcompat_appcompat",
+        "flag-junit",
+        "guava",
+        "InputRoboRNGTestsAssetsLib",
+        "platform-screenshot-diff-core",
+        "PlatformComposeSceneTransitionLayoutTestsUtils",
+    ],
+    manifest: "robo-manifest.xml",
+    aaptflags: [
+        "--extra-packages",
+        "com.android.input.screenshot",
+    ],
+    dont_merge_manifests: true,
+    platform_apis: true,
+    system_ext_specific: true,
+    certificate: "platform",
+    privileged: true,
+    resource_dirs: [],
+    kotlincflags: ["-Xjvm-default=all"],
+
+    plugins: ["dagger2-compiler"],
+    use_resource_processor: true,
+}
+
+android_robolectric_test {
+    name: "InputRoboRNGTests",
+    srcs: [
+        ":InputScreenshotTestRNGFiles",
+        ":flag-junit",
+        ":platform-test-screenshot-rules",
+    ],
+    // Do not add any new libraries here, they should be added to SystemUIGoogleRobo above.
+    static_libs: [
+        "androidx.compose.runtime_runtime",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.ext.junit",
+        "inline-mockito-robolectric-prebuilt",
+        "platform-parametric-runner-lib",
+        "uiautomator-helpers",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth",
+    ],
+    upstream: true,
+    java_resource_dirs: ["config"],
+    instrumentation_for: "InputRoboApp",
+}
diff --git a/tests/InputScreenshotTest/robotests/AndroidManifest.xml b/tests/InputScreenshotTest/robotests/AndroidManifest.xml
new file mode 100644
index 0000000..5689311
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.input.screenshot">
+    <uses-sdk android:minSdkVersion="21"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+</manifest>
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
new file mode 100644
index 0000000..baf204a
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
new file mode 100644
index 0000000..deb3cee
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
new file mode 100644
index 0000000..34e25f7
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/config/robolectric.properties b/tests/InputScreenshotTest/robotests/config/robolectric.properties
new file mode 100644
index 0000000..83d7549
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/config/robolectric.properties
@@ -0,0 +1,15 @@
+# Copyright (C) 2022 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.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/robotests/robo-manifest.xml b/tests/InputScreenshotTest/robotests/robo-manifest.xml
new file mode 100644
index 0000000..e86f58e
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/robo-manifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--- Include all the namespaces we will ever need anywhere, because this is the source the manifest merger uses for namespaces -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.input.screenshot"
+          coreApp="true">
+  <application>
+    <activity
+        android:name="androidx.activity.ComponentActivity"
+        android:exported="true">
+    </activity>
+  </application>
+</manifest>
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index c2c3d55..75dab41 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.graphics.Bitmap
+import android.os.Build
 import androidx.activity.ComponentActivity
 import androidx.compose.foundation.Image
 import androidx.compose.ui.platform.ViewRootForTest
@@ -49,15 +50,17 @@
             )
         )
     private val composeRule = createAndroidComposeRule<ComponentActivity>()
-    private val delegateRule =
-            RuleChain.outerRule(colorsRule)
-                .around(deviceEmulationRule)
+    private val roboRule =
+            RuleChain.outerRule(deviceEmulationRule)
                 .around(screenshotRule)
                 .around(composeRule)
+    private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
     private val matcher = UnitTestBitmapMatcher
+    private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
 
     override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
+        val ruleToApply = if (isRobolectric) roboRule else delegateRule
+        return ruleToApply.apply(base, description)
     }
 
     /**
@@ -84,4 +87,4 @@
         val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
         screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
     }
-}
\ No newline at end of file
+}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
index 8ae6dfd..ab7bb4e 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
@@ -26,14 +26,15 @@
 import org.junit.Test
 import org.junit.rules.RuleChain
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 
 /** A screenshot test for Keyboard layout preview for Iso physical layout. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
@@ -55,4 +56,4 @@
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java
new file mode 100644
index 0000000..4b5a56d
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java
@@ -0,0 +1,4 @@
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.input.screenshot;
+
+import org.robolectric.annotation.GraphicsMode;